diff --git a/.travis.yml b/.travis.yml index b708c2c24..e998a970a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,14 @@ sudo: false cache: bundler -before_install: - - gem uninstall bundler -aIxq --force - - gem uninstall -Ixq --force -i /home/travis/.rvm/gems/ruby-2.2.3@global bundler - - gem install bundler -v '1.12.5' - addons: apt: packages: - bsdtar rvm: - - 2.2.3 + - 2.2.5 + - 2.3.3 branches: only: diff --git a/CHANGELOG.md b/CHANGELOG.md index 0767795ee..f1263c31b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,81 @@ -## Next Version (Unreleased) +## 1.9.1 (December 7, 2016) + +IMPROVEMENTS: + + - core: Disable Vagrantfile loading when running plugin commands [GH-8066] + - guests/redhat: Detect and restart NetworkManager service if in use [GH-8052, GH-7994] + +BUG FIXES: + + - core: Detect load failures within install solution sets and retry [GH-8068] + - core: Prevent interactive shell on plugin uninstall [GH-8086, GH-8087] + - core: Remove bundler usage from Util::Env [GH-8090, GH-8094] + - guests/linux: Prevent stderr output on init version check for synced folders [GH-8051] + +## 1.9.0 (November 28, 2016) FEATURES: + - commands/box: Add `prune` subcommand for removing outdated boxes [GH-7978] + - core: Remove Bundler integration for handling internal plugins [GH-7793, GH-8000, GH-8011, GH-8031] + - providers/hyperv: Add support for Hyper-V binary configuration format + [GH-7854, GH-7706, GH-6102] + - provisioners/shell: Support MD5/SHA1 checksum validation of remote scripts [GH-7985, GH-6323] + +IMPROVEMENTS: + + - commands/plugin: Retain name sorted output when listing plugins [GH-8028] + - communicator/ssh: Support custom environment variable export template + [GH-7976, GH-6747] + - provisioners/ansible(both): Add `config_file` option to point the location of an + `ansible.cfg` file via ANSIBLE_CONFIG environment variable [GH-7195, GH-7918] + - synced_folders: Support custom naming and disable auto-mount [GH-7980, GH-6836] + +BUG FIXES: + + - guests/linux: Do not match interfaces with special characters when sorting [GH-7989, GH-7988] + - provisioner/salt: Fix Hash construction for constant [GH-7986, GH-7981] + +## 1.8.7 (November 4, 2016) + +IMPROVEMENTS: + + - guests/linux: Place ethernet devices at start of network devices list [GH-7848] + - guests/linux: Provide more consistent guest detection [GH-7887, GH-7827] + - guests/openbsd: Validate guest rsync installation success [GH-7929, GH-7898] + - guests/redhat: Include Virtuozzo Linux 7 within flavor identification [GH-7818] + - guests/windows: Allow vagrant to start Windows Nano without provisioning [GH-7831] + - provisioners/ansible_local: Change the Ansible binary detection mechanism [GH-7536] + - provisioners/ansible(both): Add the `playbook_command` option [GH-7881] + - provisioners/puppet: Support custom environment variables [GH-7931, GH-7252, GH-2270] + - util/safe_exec: Use subprocess for safe_exec on Windows [GH-7802] + - util/subprocess: Allow closing STDIN [GH-7778] + +BUG FIXES: + + - communicators/winrm: Prevent connection leakage [GH-7712] + - core: Prevent duplicate provider priorities [GH-7756] + - core: Allow Numeric type for box version [GH-7874, GH-6960] + - core: Provide friendly error when user environment is too large [GH-7889, GH-7857] + - guests: Remove `set -e` usage for better shell compatibility [GH-7921, GH-7739] + - guests/linux: Fix incorrectly configured private network [GH-7844, GH-7848] + - guests/linux: Properly order network interfaces + [GH-7866, GH-7876, GH-7858, GH-7876] + - guests/linux: Only emit upstart event if initctl is available [GH-7813] + - guests/netbsd: Fix rsync installation [GH-7922, GH-7901] + - guests/photon: Fix networking setup [GH-7808, GH-7873] + - guests/redhat: Properly configure network and restart service [GH-7751] + - guests/redhat: Prevent NetworkManager from managing devices on initial start [GH-7926] + - hosts/linux: Fix race condition in writing /etc/exports file for NFS configuration + [GH-7947, GH-7938] - Thanks to Aron Griffis (@agriffis) for identifying this issue + - plugins/rsync: Escape exclude paths [GH-7928, GH-7910] + - providers/docker: Remove --interactive flag when pty is true [GH-7688] + - provisioners/ansible_local: Use enquoted path for file/directory existence checks + - provisioners/salt: Synchronize configuration defaults with documentation [GH-7907, GH-6624] + - pushes/atlas: Fix atlas push on Windows platform [GH-6938, GH-7802] + +## 1.8.6 (September 27, 2016) + IMPROVEMENTS: - Add detection for DragonFly BSD [GH-7701] diff --git a/README.md b/README.md index bcee1166a..c9664d432 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ * Website: [https://www.vagrantup.com/](https://www.vagrantup.com/) * Source: [https://github.com/mitchellh/vagrant](https://github.com/mitchellh/vagrant) -* IRC: `#vagrant` on Freenode +* [![Gitter chat](https://badges.gitter.im/mitchellh/vagrant.png)](https://gitter.im/mitchellh/vagrant) * Mailing list: [Google Groups](https://groups.google.com/group/vagrant-up) Vagrant is a tool for building and distributing development environments. @@ -45,11 +45,7 @@ To learn how to build a fully functional development environment, follow the ## Installing the Gem from Git If you want the bleeding edge version of Vagrant, we try to keep master pretty stable -and you're welcome to give it a shot. The following is an example showing how to do this: - - rake install - -Ruby 2.0 is needed. +and you're welcome to give it a shot. Please review the installation page [here](https://www.vagrantup.com/docs/installation/source.html). ## Contributing to Vagrant @@ -66,11 +62,6 @@ like so: bundle exec vagrant help -**NOTE:** By default running Vagrant via `bundle` will disable plugins. -This is necessary because Vagrant creates its own private Bundler context -(it does not respect your Gemfile), because it uses Bundler to manage plugin -dependencies. - ### Acceptance Tests Vagrant also comes with an acceptance test suite that does black-box diff --git a/bin/vagrant b/bin/vagrant index b9ef5feee..080b7a7c8 100755 --- a/bin/vagrant +++ b/bin/vagrant @@ -8,6 +8,11 @@ Signal.trap("INT") { abort } # Split arguments by "--" if its there, we'll recombine them later argv = ARGV.dup argv_extra = [] + +# These will be the options that are passed to initialze the Vagrant +# environment. +opts = {} + if idx = argv.index("--") argv_extra = argv.slice(idx+1, argv.length-2) argv = argv.slice(0, idx) @@ -20,43 +25,26 @@ if argv.include?("-v") || argv.include?("--version") exit 0 end -# This is kind of hacky, and I'd love to find a better way to do this, but -# if we're accessing the plugin interface, we want to NOT load plugins -# for this run, because they can actually interfere with the function -# of the plugin interface. +# Disable plugin loading for commands where plugins are not required. This will +# also disable loading of the Vagrantfile if it available as the environment +# is not required for these commands argv.each_index do |i| arg = argv[i] if !arg.start_with?("-") - if arg == "plugin" - ENV["VAGRANT_NO_PLUGINS"] = "1" - ENV["VAGRANT_VAGRANTFILE"] = "plugin_command_#{Time.now.to_i}" + if ["plugin", "help"].include?(arg) || (arg == "box" && argv[i+1] == "list") + opts[:vagrantfile_name] = "" + ENV['VAGRANT_NO_PLUGINS'] = "1" end - if arg == "help" - ENV["VAGRANT_VAGRANTFILE"] = "plugin_command_#{Time.now.to_i}" - end - - if arg == "box" && argv[i+1] == "list" - ENV["VAGRANT_VAGRANTFILE"] = "plugin_command_#{Time.now.to_i}" + if arg == "plugin" && argv[i+1] != "list" + ENV['VAGRANT_DISABLE_PLUGIN_INIT'] = "1" end break end end -# First, make sure that we're executing using the proper Bundler context -# with our plugins. If we're not, then load that and reload Vagrant. -if !ENV["VAGRANT_INTERNAL_BUNDLERIZED"] - require "rbconfig" - ruby_path = File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["ruby_install_name"]) - Kernel.exec( - ruby_path, - File.expand_path("../../lib/vagrant/pre-rubygems.rb", __FILE__), - *ARGV) - raise "Fatal error: this line should never be reached" -end - # Set logging level to `debug`. This is done before loading 'vagrant', as it # sets up the logging system. if argv.include?("--debug") @@ -64,37 +52,6 @@ if argv.include?("--debug") ENV["VAGRANT_LOG"] = "debug" end -# Setup our dependencies by initializing Bundler. If we're using plugins, -# then also initialize the paths to the plugins. -require "bundler" -begin - $vagrant_bundler_runtime = Bundler.setup(:default, :plugins) -rescue Bundler::GemNotFound - $stderr.puts "Bundler, the underlying system used to manage Vagrant plugins," - $stderr.puts "is reporting that a plugin or its dependency can't be found." - $stderr.puts "This is usually caused by manual tampering with the 'plugins.json'" - $stderr.puts "file in the Vagrant home directory. To fix this error, please" - $stderr.puts "remove that file and reinstall all your plugins using `vagrant" - $stderr.puts "plugin install`." -rescue Bundler::VersionConflict => e - $stderr.puts "Vagrant experienced a version conflict with some installed plugins!" - $stderr.puts "This usually happens if you recently upgraded Vagrant. As part of the" - $stderr.puts "upgrade process, some existing plugins are no longer compatible with" - $stderr.puts "this version of Vagrant. The recommended way to fix this is to remove" - $stderr.puts "your existing plugins and reinstall them one-by-one. To remove all" - $stderr.puts "plugins:" - $stderr.puts "" - $stderr.puts " rm -r ~/.vagrant.d/plugins.json ~/.vagrant.d/gems" - $stderr.puts "" - $stderr.puts "Note if you have an alternate VAGRANT_HOME environmental variable" - $stderr.puts "set, the folders above will be in that directory rather than your" - $stderr.puts "user's home directory." - $stderr.puts "" - $stderr.puts "The error message is shown below:\n\n" - $stderr.puts e.message - exit 1 -end - # Stdout/stderr should not buffer output $stdout.sync = true $stderr.sync = true @@ -114,10 +71,6 @@ begin logger = Log4r::Logger.new("vagrant::bin::vagrant") logger.info("`vagrant` invoked: #{ARGV.inspect}") - # These will be the options that are passed to initialze the Vagrant - # environment. - opts = {} - # Disable color in a few cases: # # * --no-color is anywhere in our arguments diff --git a/lib/vagrant.rb b/lib/vagrant.rb index a84bb1b05..385869321 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -1,12 +1,5 @@ require "vagrant/shared_helpers" -if Vagrant.plugins_enabled? && !defined?(Bundler) - puts "It appears that Vagrant was not properly loaded. Specifically," - puts "the bundler context Vagrant requires was not setup. Please execute" - puts "vagrant using only the `vagrant` executable." - abort -end - require 'rubygems' require 'log4r' @@ -72,11 +65,6 @@ global_logger.info("RubyGems version: #{Gem::VERSION}") ENV.each do |k, v| global_logger.info("#{k}=#{v.inspect}") if k =~ /^VAGRANT_/ end -global_logger.info("Plugins:") -Bundler.definition.specs_for([:plugins]).each do |spec| - global_logger.info(" - #{spec.name} = #{spec.version}") -end - # We need these components always so instead of an autoload we # just require them explicitly here. @@ -254,6 +242,35 @@ if I18n.config.respond_to?(:enforce_available_locales=) I18n.config.enforce_available_locales = true end +# Setup the plugin manager and load any defined plugins +require_relative "vagrant/plugin/manager" +plugins = Vagrant::Plugin::Manager.instance.installed_plugins + +global_logger.info("Plugins:") +plugins.each do |plugin_name, plugin_info| + installed_version = plugin_info["installed_gem_version"] + version_constraint = plugin_info["gem_version"] + installed_version = 'undefined' if installed_version.to_s.empty? + version_constraint = '> 0' if version_constraint.to_s.empty? + global_logger.info( + " - #{plugin_name} = [installed: " \ + "#{installed_version} constraint: " \ + "#{version_constraint}]" + ) +end + +if Vagrant.plugins_init? + begin + Vagrant::Bundler.instance.init!(plugins) + rescue Exception => e + global_logger.error("Plugin initialization error - #{e.class}: #{e}") + e.backtrace.each do |backtrace_line| + global_logger.debug(backtrace_line) + end + raise Vagrant::Errors::PluginInitError, message: e.to_s + end +end + # A lambda that knows how to load plugins from a single directory. plugin_load_proc = lambda do |directory| # We only care about directories @@ -288,8 +305,40 @@ end if Vagrant.plugins_enabled? begin global_logger.info("Loading plugins!") - $vagrant_bundler_runtime.require(:plugins) + plugins.each do |plugin_name, plugin_info| + if plugin_info["require"].to_s.empty? + begin + global_logger.debug("Loading plugin `#{plugin_name}` with default require: `#{plugin_name}`") + require plugin_name + rescue LoadError, Gem::LoadError => load_error + if plugin_name.include?("-") + begin + plugin_slash = plugin_name.gsub("-", "/") + global_logger.debug("Failed to load plugin `#{plugin_name}` with default require.") + global_logger.debug("Loading plugin `#{plugin_name}` with slash require: `#{plugin_slash}`") + require plugin_slash + rescue LoadError, Gem::LoadError + raise load_error + end + end + end + else + global_logger.debug("Loading plugin `#{plugin_name}` with custom require: `#{plugin_info["require"]}`") + require plugin_info["require"] + end + global_logger.debug("Successfully loaded plugin `#{plugin_name}`.") + end + if defined?(::Bundler) + global_logger.debug("Bundler detected in use. Loading `:plugins` group.") + ::Bundler.require(:plugins) + end rescue Exception => e + global_logger.error("Plugin loading error: #{e.class} - #{e}") + e.backtrace.each do |backtrace_line| + global_logger.debug(backtrace_line) + end raise Vagrant::Errors::PluginLoadError, message: e.to_s end +else + global_logger.debug("Plugin loading is currently disabled.") end diff --git a/lib/vagrant/bundler.rb b/lib/vagrant/bundler.rb index ff152f7b7..2a70c7191 100644 --- a/lib/vagrant/bundler.rb +++ b/lib/vagrant/bundler.rb @@ -4,7 +4,9 @@ require "set" require "tempfile" require "fileutils" -require "bundler" +require "rubygems/package" +require "rubygems/uninstaller" +require "rubygems/name_tuple" require_relative "shared_helpers" require_relative "version" @@ -15,83 +17,77 @@ module Vagrant # Bundler as a way to properly resolve all dependencies of Vagrant and # all Vagrant-installed plugins. class Bundler + + HASHICORP_GEMSTORE = 'https://gems.hashicorp.com'.freeze + def self.instance @bundler ||= self.new end + attr_reader :plugin_gem_path + def initialize - @enabled = true if ENV["VAGRANT_INSTALLER_ENV"] || - ENV["VAGRANT_FORCE_BUNDLER"] - @enabled = !::Bundler::SharedHelpers.in_bundle? if !@enabled - @monitor = Monitor.new - - @gem_home = ENV["GEM_HOME"] - @gem_path = ENV["GEM_PATH"] - - # Set the Bundler UI to be a silent UI. We have to add the - # `silence` method to it because Bundler UI doesn't have it. - ::Bundler.ui = - if ::Bundler::UI.const_defined? :Silent - # bundler >= 1.6.0, we use our custom UI - BundlerUI.new - else - # bundler < 1.6.0 - ::Bundler::UI.new - end - if !::Bundler.ui.respond_to?(:silence) - ui = ::Bundler.ui - def ui.silence(*args) - yield - end - end + @plugin_gem_path = Vagrant.user_data_path.join("gems", RUBY_VERSION).freeze + @logger = Log4r::Logger.new("vagrant::bundler") end # Initializes Bundler and the various gem paths so that we can begin # loading gems. This must only be called once. - def init!(plugins) - # If we're not enabled, then we don't do anything. - return if !@enabled + def init!(plugins, repair=false) + # Add HashiCorp RubyGems source + Gem.sources << HASHICORP_GEMSTORE - bundle_path = Vagrant.user_data_path.join("gems") - - # Setup the "local" Bundler configuration. We need to set BUNDLE_PATH - # because the existence of this actually suppresses `sudo`. - @appconfigpath = Dir.mktmpdir("vagrant-bundle-app-config") - File.open(File.join(@appconfigpath, "config"), "w+") do |f| - f.write("BUNDLE_PATH: \"#{bundle_path}\"") + # Generate dependencies for all registered plugins + plugin_deps = plugins.map do |name, info| + Gem::Dependency.new(name, info['gem_version'].to_s.empty? ? '> 0' : info['gem_version']) end - # Setup the Bundler configuration - @configfile = tempfile("vagrant-configfile") - @configfile.close + @logger.debug("Current generated plugin dependency list: #{plugin_deps}") - # Build up the Gemfile for our Bundler context. We make sure to - # lock Vagrant to our current Vagrant version. In addition to that, - # we add all our plugin dependencies. - @gemfile = build_gemfile(plugins) + # Load dependencies into a request set for resolution + request_set = Gem::RequestSet.new(*plugin_deps) + # Never allow dependencies to be remotely satisfied during init + request_set.remote = false - Util::SafeEnv.change_env do |env| - # Set the environmental variables for Bundler - env["BUNDLE_APP_CONFIG"] = @appconfigpath - env["BUNDLE_CONFIG"] = @configfile.path - env["BUNDLE_GEMFILE"] = @gemfile.path - env["BUNDLE_RETRY"] = "3" - env["GEM_PATH"] = - "#{bundle_path}#{::File::PATH_SEPARATOR}#{@gem_path}" + repair_result = nil + begin + # Compose set for resolution + composed_set = generate_vagrant_set + # Resolve the request set to ensure proper activation order + solution = request_set.resolve(composed_set) + rescue Gem::UnsatisfiableDependencyError => failure + if repair + raise failure if @init_retried + @logger.debug("Resolution failed but attempting to repair. Failure: #{failure}") + install(plugins) + @init_retried = true + retry + else + raise + end end - Gem.clear_paths + # Activate the gems + activate_solution(solution) + + full_vagrant_spec_list = Gem::Specification.find_all{true} + + solution.map(&:full_spec) + + if(defined?(::Bundler)) + @logger.debug("Updating Bundler with full specification list") + ::Bundler.rubygems.replace_entrypoints(full_vagrant_spec_list) + end + + Gem.post_reset do + Gem::Specification.all = full_vagrant_spec_list + end + + Gem::Specification.reset end # Removes any temporary files created by init def deinit - # If we weren't enabled, then we don't do anything. - return if !@enabled - - FileUtils.rm_rf(ENV["BUNDLE_APP_CONFIG"]) rescue nil - FileUtils.rm_f(ENV["BUNDLE_CONFIG"]) rescue nil - FileUtils.rm_f(ENV["BUNDLE_GEMFILE"]) rescue nil - FileUtils.rm_f(ENV["BUNDLE_GEMFILE"]+".lock") rescue nil + # no-op end # Installs the list of plugins. @@ -106,35 +102,17 @@ module Vagrant # # @param [String] path Path to a local gem file. # @return [Gem::Specification] - def install_local(path) - # We have to do this load here because this file can be loaded - # before RubyGems is actually loaded. - require "rubygems/dependency_installer" - begin - require "rubygems/format" - rescue LoadError - # rubygems 2.x - end - - # If we're installing from a gem file, determine the name - # based on the spec in the file. - pkg = if defined?(Gem::Format) - # RubyGems 1.x - Gem::Format.from_file_by_path(path) - else - # RubyGems 2.x - Gem::Package.new(path) - end - - # Install the gem manually. If the gem exists locally, then - # Bundler shouldn't attempt to get it remotely. - with_isolated_gem do - installer = Gem::DependencyInstaller.new( - document: [], prerelease: false) - installer.install(path, "= #{pkg.spec.version}") - end - - pkg.spec + def install_local(path, opts={}) + plugin_source = Gem::Source::SpecificFile.new(path) + plugin_info = { + plugin_source.spec.name => { + "local_source" => plugin_source, + "sources" => opts.fetch(:sources, Gem.sources.map(&:to_s)) + } + } + @logger.debug("Installing local plugin - #{plugin_info}") + internal_install(plugin_info, {}) + plugin_source.spec end # Update updates the given plugins, or every plugin if none is given. @@ -144,277 +122,319 @@ module Vagrant # empty or nil, all plugins will be updated. def update(plugins, specific) specific ||= [] - update = true - update = { gems: specific } if !specific.empty? + update = {gems: specific.empty? ? true : specific} internal_install(plugins, update) end # Clean removes any unused gems. def clean(plugins) - gemfile = build_gemfile(plugins) - lockfile = "#{gemfile.path}.lock" - definition = ::Bundler::Definition.build(gemfile, lockfile, nil) - root = File.dirname(gemfile.path) + @logger.debug("Cleaning Vagrant plugins of stale gems.") + # Generate dependencies for all registered plugins + plugin_deps = plugins.map do |name, info| + gem_version = info['installed_gem_version'] + gem_version = info['gem_version'] if gem_version.to_s.empty? + gem_version = "> 0" if gem_version.to_s.empty? + Gem::Dependency.new(name, gem_version) + end - with_isolated_gem do - runtime = ::Bundler::Runtime.new(root, definition) - runtime.clean + @logger.debug("Current plugin dependency list: #{plugin_deps}") + + # Load dependencies into a request set for resolution + request_set = Gem::RequestSet.new(*plugin_deps) + # Never allow dependencies to be remotely satisfied during cleaning + request_set.remote = false + + # Sets that we can resolve our dependencies from. Note that we only + # resolve from the current set as all required deps are activated during + # init. + current_set = generate_vagrant_set + + # Collect all plugin specifications + plugin_specs = Dir.glob(plugin_gem_path.join('specifications/*.gemspec').to_s).map do |spec_path| + Gem::Specification.load(spec_path) + end + + @logger.debug("Generating current plugin state solution set.") + + # Resolve the request set to ensure proper activation order + solution = request_set.resolve(current_set) + solution_specs = solution.map(&:full_spec) + solution_full_names = solution_specs.map(&:full_name) + + # Find all specs installed to plugins directory that are not + # found within the solution set + plugin_specs.delete_if do |spec| + solution_full_names.include?(spec.full_name) + end + + @logger.debug("Specifications to be removed - #{plugin_specs.map(&:full_name)}") + + # Now delete all unused specs + plugin_specs.each do |spec| + @logger.debug("Uninstalling gem - #{spec.full_name}") + Gem::Uninstaller.new(spec.name, + version: spec.version, + install_dir: plugin_gem_path, + all: true, + executables: true, + force: true, + ignore: true, + ).uninstall_gem(spec) + end + + solution.find_all do |spec| + plugins.keys.include?(spec.name) end end # During the duration of the yielded block, Bundler loud output # is enabled. def verbose - @monitor.synchronize do - begin - old_ui = ::Bundler.ui - require 'bundler/vendored_thor' - ::Bundler.ui = ::Bundler::UI::Shell.new - yield - ensure - ::Bundler.ui = old_ui - end + if block_given? + initial_state = @verbose + @verbose = true + yield + @verbose = initial_state + else + @verbose = true end end protected - # Builds a valid Gemfile for use with Bundler given the list of - # plugins. - # - # @return [Tempfile] - def build_gemfile(plugins) - sources = plugins.values.map { |p| p["sources"] }.flatten.compact.uniq + def internal_install(plugins, update, **extra) + # Only allow defined Gem sources + Gem.sources.clear - f = tempfile("vagrant-gemfile") - f.tap do |gemfile| - sources.each do |source| - next if source == "" - gemfile.puts(%Q[source "#{source}"]) + update = {} if !update.is_a?(Hash) + skips = [] + installer_set = Gem::Resolver::InstallerSet.new(:both) + + # Generate all required plugin deps + plugin_deps = plugins.map do |name, info| + if update[:gems] == true || (update[:gems].respond_to?(:include?) && update[:gems].include?(name)) + gem_version = '> 0' + skips << name + else + gem_version = info['gem_version'].to_s.empty? ? '> 0' : info['gem_version'] end + if plugin_source = info.delete("local_source") + installer_set.add_local(plugin_source.spec.name, plugin_source.spec, plugin_source) + end + Array(info["sources"]).each do |source| + if !Gem.sources.include?(source) + @logger.debug("Adding RubyGems source for plugin install: #{source}") + Gem.sources << source + end + end + Gem::Dependency.new(name, gem_version) + end - gemfile.puts(%Q[gem "vagrant", "= #{VERSION}"]) + @logger.debug("Dependency list for installation: #{plugin_deps}") - gemfile.puts("group :plugins do") - plugins.each do |name, plugin| - version = plugin["gem_version"] - version = nil if version == "" + # Create the request set for the new plugins + request_set = Gem::RequestSet.new(*plugin_deps) - opts = {} - if plugin["require"] && plugin["require"] != "" - opts[:require] = plugin["require"] + installer_set = Gem::Resolver.compose_sets( + installer_set, + generate_builtin_set, + generate_plugin_set(skips) + ) + + @logger.debug("Generating solution set for installation.") + + # Generate the required solution set for new plugins + solution = request_set.resolve(installer_set) + activate_solution(solution) + + @logger.debug("Installing required gems.") + + # Install all remote gems into plugin path. Set the installer to ignore dependencies + # as we know the dependencies are satisfied and it will attempt to validate a gem's + # dependencies are satisified by gems in the install directory (which will likely not + # be true) + result = request_set.install_into(plugin_gem_path.to_s, true, ignore_dependencies: true) + result = result.map(&:full_spec) + result + end + + # Generate the composite resolver set totally all of vagrant (builtin + plugin set) + def generate_vagrant_set + Gem::Resolver.compose_sets(generate_builtin_set, generate_plugin_set) + end + + # @return [Array<[Gem::Specification, String]>] spec and directory pairs + def vagrant_internal_specs + list = {} + directories = [Gem::Specification.default_specifications_dir] + Gem::Specification.find_all{true}.each do |spec| + list[spec.full_name] = spec + end + if(!defined?(::Bundler)) + directories += Gem::Specification.dirs.find_all do |path| + !path.start_with?(Gem.user_dir) + end + end + Gem::Specification.each_spec(directories) do |spec| + if !list[spec.full_name] + list[spec.full_name] = spec + end + end + list.values + end + + # Generate the builtin resolver set + def generate_builtin_set + builtin_set = BuiltinSet.new + @logger.debug("Generating new builtin set instance.") + vagrant_internal_specs.each do |spec| + builtin_set.add_builtin_spec(spec) + end + builtin_set + end + + # Generate the plugin resolver set. Optionally provide specification names (short or + # full) that should be ignored + def generate_plugin_set(skip=[]) + plugin_set = PluginSet.new + @logger.debug("Generating new plugin set instance. Skip gems - #{skip}") + Dir.glob(plugin_gem_path.join('specifications/*.gemspec').to_s).each do |spec_path| + spec = Gem::Specification.load(spec_path) + desired_spec_path = File.join(spec.gem_dir, "#{spec.name}.gemspec") + # Vendor set requires the spec to be within the gem directory. Some gems will package their + # spec file, and that's not what we want to load. + if !File.exist?(desired_spec_path) || !FileUtils.cmp(spec.spec_file, desired_spec_path) + File.write(desired_spec_path, spec.to_ruby) + end + next if skip.include?(spec.name) || skip.include?(spec.full_name) + plugin_set.add_vendor_gem(spec.name, spec.gem_dir) + end + plugin_set + end + + # Activate a given solution + def activate_solution(solution) + retried = false + begin + @logger.debug("Activating solution set: #{solution.map(&:full_name)}") + solution.each do |activation_request| + unless activation_request.full_spec.activated? + @logger.debug("Activating gem #{activation_request.full_spec.full_name}") + activation_request.full_spec.activate + if(defined?(::Bundler)) + @logger.debug("Marking gem #{activation_request.full_spec.full_name} loaded within Bundler.") + ::Bundler.rubygems.mark_loaded activation_request.full_spec + end + end + end + rescue Gem::LoadError => e + # Depending on the version of Ruby, the ordering of the solution set + # will be either 0..n (molinillo) or n..0 (pre-molinillo). Instead of + # attempting to determine what's in use, or if it has some how changed + # again, just reverse order on failure and attempt again. + if retried + @logger.error("Failed to load solution set - #{e.class}: #{e}") + matcher = e.message.match(/Could not find '(?[^']+)'/) + if matcher && !matcher["gem_name"].empty? + desired_activation_request = solution.detect do |request| + request.name == matcher["gem_name"] + end + if desired_activation_request && !desired_activation_request.full_spec.activated? + activation_request = desired_activation_request + @logger.warn("Found misordered activation request for #{desired_activation_request.full_name}. Moving to solution HEAD.") + solution.delete(desired_activation_request) + solution.unshift(desired_activation_request) + retry + end end - gemfile.puts(%Q[gem "#{name}", #{version.inspect}, #{opts.inspect}]) + raise + else + @logger.debug("Failed to load solution set. Retrying with reverse order.") + retried = true + solution.reverse! + retry end - gemfile.puts("end") - gemfile.close end end - # This installs a set of plugins and optionally updates those gems. - # - # @param [Hash] plugins - # @param [Hash, Boolean] update If true, updates all plugins, otherwise - # can be a hash of options. See Bundler.definition. - # @return [Array] - def internal_install(plugins, update, **extra) - gemfile = build_gemfile(plugins) - lockfile = "#{gemfile.path}.lock" - definition = ::Bundler::Definition.build(gemfile, lockfile, update) - root = File.dirname(gemfile.path) - opts = {} - opts["local"] = true if extra[:local] - - with_isolated_gem do - ::Bundler::Installer.install(root, definition, opts) - end - - # TODO(mitchellh): clean gems here... for some reason when I put - # it in on install, we get a GemNotFound exception. Gotta investigate. - - definition.specs - rescue ::Bundler::VersionConflict => e - raise Errors::PluginInstallVersionConflict, - conflicts: e.to_s.gsub("Bundler", "Vagrant") - rescue ::Bundler::BundlerError => e - if !::Bundler.ui.is_a?(BundlerUI) - raise - end - - # Add the warn/error level output from Bundler if we have any - message = "#{e.message}" - if ::Bundler.ui.output != "" - message += "\n\n#{::Bundler.ui.output}" - end - - raise ::Bundler::BundlerError, message - end - - def with_isolated_gem - raise Errors::BundlerDisabled if !@enabled - - tmp_gemfile = tempfile("vagrant-gemfile") - tmp_gemfile.close - - # Remove bundler settings so that Bundler isn't loaded when building - # native extensions because it causes all sorts of problems. - old_rubyopt = ENV["RUBYOPT"] - old_gemfile = ENV["BUNDLE_GEMFILE"] - ENV["BUNDLE_GEMFILE"] = tmp_gemfile.path - ENV["RUBYOPT"] = (ENV["RUBYOPT"] || "").gsub(/-rbundler\/setup\s*/, "") - - # Set the GEM_HOME so gems are installed only to our local gem dir - ENV["GEM_HOME"] = Vagrant.user_data_path.join("gems").to_s - - # Clear paths so that it reads the new GEM_HOME setting - Gem.paths = ENV - - # Reset the all specs override that Bundler does - old_all = Gem::Specification._all - - # WARNING: Seriously don't touch this without reading the comment attached - # to the monkey-patch at the bottom of this file. - Gem::Specification.vagrant_reset! - - # /etc/gemrc and so on. - old_config = nil - begin - old_config = Gem.configuration - rescue Psych::SyntaxError - # Just ignore this. This means that the ".gemrc" file has - # an invalid syntax and can't be loaded. We don't care, because - # when we set Gem.configuration to nil later, it'll force a reload - # if it is needed. - end - Gem.configuration = NilGemConfig.new - - # Use a silent UI so that we have no output - Gem::DefaultUserInteraction.use_ui(Gem::SilentUI.new) do - return yield - end - ensure - tmp_gemfile.unlink rescue nil - - ENV["BUNDLE_GEMFILE"] = old_gemfile - ENV["GEM_HOME"] = @gem_home - ENV["RUBYOPT"] = old_rubyopt - - Gem.configuration = old_config - Gem.paths = ENV - Gem::Specification.all = old_all - end - - # This method returns a proper "tempfile" on disk. Ruby's Tempfile class - # would work really great for this, except GC can come along and remove - # the file before we are done with it. This is because we "close" the file, - # but we might be shelling out to a subprocess. - # - # @return [File] - def tempfile(name) - path = Dir::Tmpname.create(name) {} - return File.open(path, "w+") - end - - # This is pretty hacky but it is a custom implementation of - # Gem::ConfigFile so that we don't load any gemrc files. - class NilGemConfig < Gem::ConfigFile + # This is a custom Gem::Resolver::Set for use with vagrant "system" gems. It + # allows the installed set of gems to be used for providing a solution while + # enforcing strict constraints. This ensures that plugins cannot "upgrade" + # gems that are builtin to vagrant itself. + class BuiltinSet < Gem::Resolver::Set def initialize - # We _can not_ `super` here because that can really mess up - # some other configuration state. We need to just set everything - # directly. + super + @remote = false + @specs = [] + end - @api_keys = {} - @args = [] - @backtrace = false - @bulk_threshold = 1000 - @hash = {} - @update_sources = true - @verbose = true + def add_builtin_spec(spec) + @specs.push(spec).uniq! + end + + def find_all(req) + @specs.select do |spec| + req.match?(spec) + end.map do |spec| + Gem::Resolver::InstalledSpecification.new(self, spec) + end end end - # This monkey patches Gem::Specification from RubyGems to add a new method, - # `vagrant_reset!`. For some background, Vagrant needs to set the value - # of these variables to nil to force new specs to be loaded. Previously, - # this was accomplished by setting Gem::Specification.specs = nil. However, - # newer versions of Rubygems try to map across that nil using a group_by - # clause, breaking things. - # - # This generally never affected Vagrant users who were using the official - # Vagrant installers because we lock to an older version of Rubygems that - # does not have this issue. The users of the official debian packages, - # however, experienced this issue because they float on Rubygems. - # - # In GH-7073, a number of Debian users reported this issue, but it was not - # reproducible in the official installer for reasons described above. Commit - # ba77d4b switched to using Gem::Specification.reset, but this actually - # broke the ability to install gems locally (GH-7493) because it resets - # the complete local cache, which is already built. - # - # The only solution that works with both new and old versions of Rubygems - # is to provide our own function for JUST resetting all the stubs. Both - # @@all and @@stubs must be set to a falsey value, so some of the - # originally-suggested solutions of using an empty array do not work. Only - # setting these values to nil (without clearing the cache), allows Vagrant - # to install and manage plugins. - class Gem::Specification < Gem::BasicSpecification - def self.vagrant_reset! - @@all = @@stubs = nil + # This is a custom Gem::Resolver::Set for use with Vagrant plugins. It is + # a modified Gem::Resolver::VendorSet that supports multiple versions of + # a specific gem + class PluginSet < Gem::Resolver::VendorSet + ## + # Adds a specification to the set with the given +name+ which has been + # unpacked into the given +directory+. + def add_vendor_gem(name, directory) + gemspec = File.join(directory, "#{name}.gemspec") + spec = Gem::Specification.load(gemspec) + if !spec + raise Gem::GemNotFoundException, + "unable to find #{gemspec} for gem #{name}" + end + + spec.full_gem_path = File.expand_path(directory) + + @specs[spec.name] ||= [] + @specs[spec.name] << spec + @directories[spec] = directory + + spec end - end - if ::Bundler::UI.const_defined? :Silent - class BundlerUI < ::Bundler::UI::Silent - attr_reader :output - - def initialize - @output = "" + ## + # Returns an Array of VendorSpecification objects matching the + # DependencyRequest +req+. + def find_all(req) + @specs.values.flatten.select do |spec| + req.match?(spec) + end.map do |spec| + source = Gem::Source::Vendor.new(@directories[spec]) + Gem::Resolver::VendorSpecification.new(self, spec, source) end + end - def info(message, newline = nil) - end - - def confirm(message, newline = nil) - end - - def warn(message, newline = nil) - @output += message - @output += "\n" if newline - end - - def error(message, newline = nil) - @output += message - @output += "\n" if newline - end - - def debug(message, newline = nil) - end - - def debug? - false - end - - def quiet? - false - end - - def ask(message) - end - - def level=(name) - end - - def level(name = nil) - "info" - end - - def trace(message, newline = nil) - end - - def silence - yield - end + ## + # Loads a spec with the given +name+. +version+, +platform+ and +source+ are + # ignored. + def load_spec (name, version, platform, source) + version = Gem::Version.new(version) if !version.is_a?(Gem::Version) + @specs.fetch(name, []).detect{|s| s.name == name && s.version = version} + end + end + end +end + +# Patch for Ruby 2.2 and Bundler to behave properly when uninstalling plugins +if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3') + if defined?(::Bundler) && !::Bundler::SpecSet.instance_methods.include?(:delete) + class Gem::Specification + def self.remove_spec(spec) + Gem::Specification.reset end end end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 5f936c0fe..96c9adafd 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -338,7 +338,7 @@ module Vagrant # a priority to each in the order they exist so that we try these first. config = {} root_config.vm.__providers.reverse.each_with_index do |key, idx| - config[key] = idx + config[key] = idx + 1 end # Determine the max priority so that we can add the config priority diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index a557f88d8..494f0071b 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -340,6 +340,10 @@ module Vagrant error_key(:downloader_interrupted) end + class DownloaderChecksumError < VagrantError + error_key(:downloader_checksum_error) + end + class EnvInval < VagrantError error_key(:env_inval) end @@ -456,6 +460,10 @@ module Vagrant error_key(:nfs_bad_exports) end + class NFSExportsFailed < VagrantError + error_key(:nfs_exports_failed) + end + class NFSCantReadExports < VagrantError error_key(:nfs_cant_read_exports) end @@ -580,6 +588,10 @@ module Vagrant error_key(:plugin_uninstall_system) end + class PluginInitError < VagrantError + error_key(:plugin_init_error) + end + class PushesNotDefined < VagrantError error_key(:pushes_not_defined) end @@ -608,6 +620,10 @@ module Vagrant error_key(:rsync_not_installed_in_guest) end + class RSyncGuestInstallError < VagrantError + error_key(:rsync_guest_install_error) + end + class SCPPermissionDenied < VagrantError error_key(:scp_permission_denied) end diff --git a/lib/vagrant/plugin/manager.rb b/lib/vagrant/plugin/manager.rb index dfaab2ecf..4f875fd6f 100644 --- a/lib/vagrant/plugin/manager.rb +++ b/lib/vagrant/plugin/manager.rb @@ -44,10 +44,9 @@ module Vagrant local = false if name =~ /\.gem$/ # If this is a gem file, then we install that gem locally. - local_spec = Vagrant::Bundler.instance.install_local(name) + local_spec = Vagrant::Bundler.instance.install_local(name, opts) name = local_spec.name opts[:version] = local_spec.version.to_s - local = true end plugins = installed_plugins @@ -57,33 +56,41 @@ module Vagrant "sources" => opts[:sources], } - result = nil - install_lambda = lambda do - Vagrant::Bundler.instance.install(plugins, local).each do |spec| - next if spec.name != name - next if result && result.version >= spec.version - result = spec + if local_spec.nil? + result = nil + install_lambda = lambda do + Vagrant::Bundler.instance.install(plugins, local).each do |spec| + next if spec.name != name + next if result && result.version >= spec.version + result = spec + end end - end - if opts[:verbose] - Vagrant::Bundler.instance.verbose(&install_lambda) + if opts[:verbose] + Vagrant::Bundler.instance.verbose(&install_lambda) + else + install_lambda.call + end else - install_lambda.call + result = local_spec end - # Add the plugin to the state file @user_file.add_plugin( result.name, version: opts[:version], require: opts[:require], sources: opts[:sources], + installed_gem_version: result.version.to_s ) + # After install clean plugin gems to remove any cruft. This is useful + # for removing outdated dependencies or other versions of an installed + # plugin if the plugin is upgraded/downgraded + Vagrant::Bundler.instance.clean(installed_plugins) result - rescue ::Bundler::GemNotFound + rescue Gem::GemNotFoundException raise Errors::PluginGemNotFound, name: name - rescue ::Bundler::BundlerError => e + rescue Gem::Exception => e raise Errors::BundlerError, message: e.to_s end @@ -102,14 +109,30 @@ module Vagrant # Clean the environment, removing any old plugins Vagrant::Bundler.instance.clean(installed_plugins) - rescue ::Bundler::BundlerError => e + rescue Gem::Exception => e raise Errors::BundlerError, message: e.to_s end # Updates all or a specific set of plugins. def update_plugins(specific) - Vagrant::Bundler.instance.update(installed_plugins, specific) - rescue ::Bundler::BundlerError => e + result = Vagrant::Bundler.instance.update(installed_plugins, specific) + installed_plugins.each do |name, info| + matching_spec = result.detect{|s| s.name == name} + info = Hash[ + info.map do |key, value| + [key.to_sym, value] + end + ] + if matching_spec + @user_file.add_plugin(name, **info.merge( + version: "> 0", + installed_gem_version: matching_spec.version.to_s + )) + end + end + Vagrant::Bundler.instance.clean(installed_plugins) + result + rescue Gem::Exception => e raise Errors::BundlerError, message: e.to_s end @@ -123,8 +146,14 @@ module Vagrant system[k] = v.merge("system" => true) end end + plugin_list = system.merge(@user_file.installed_plugins) - system.merge(@user_file.installed_plugins) + # Sort plugins by name + Hash[ + plugin_list.map{|plugin_name, plugin_info| + [plugin_name, plugin_info] + }.sort_by(&:first) + ] end # This returns the list of plugins that are installed as diff --git a/lib/vagrant/plugin/state_file.rb b/lib/vagrant/plugin/state_file.rb index c80ee658c..85db50b92 100644 --- a/lib/vagrant/plugin/state_file.rb +++ b/lib/vagrant/plugin/state_file.rb @@ -31,11 +31,12 @@ module Vagrant # @param [String] name The name of the plugin def add_plugin(name, **opts) @data["installed"][name] = { - "ruby_version" => RUBY_VERSION, - "vagrant_version" => Vagrant::VERSION, - "gem_version" => opts[:version] || "", - "require" => opts[:require] || "", - "sources" => opts[:sources] || [], + "ruby_version" => RUBY_VERSION, + "vagrant_version" => Vagrant::VERSION, + "gem_version" => opts[:version] || "", + "require" => opts[:require] || "", + "sources" => opts[:sources] || [], + "installed_gem_version" => opts[:installed_gem_version] } save! diff --git a/lib/vagrant/pre-rubygems.rb b/lib/vagrant/pre-rubygems.rb deleted file mode 100644 index 63b46f594..000000000 --- a/lib/vagrant/pre-rubygems.rb +++ /dev/null @@ -1,34 +0,0 @@ -# This file is to be loaded _before_ any RubyGems are loaded. This file -# initializes the Bundler context so that Vagrant and its associated plugins -# can load properly, and then execs out into Vagrant again. - -require_relative "shared_helpers" - -if defined?(Bundler) - require "bundler/shared_helpers" - if Bundler::SharedHelpers.in_bundle? && !Vagrant.very_quiet? - puts "Vagrant appears to be running in a Bundler environment. Your " - puts "existing Gemfile will be used. Vagrant will not auto-load any plugins" - puts "installed with `vagrant plugin`. Vagrant will autoload any plugins in" - puts "the 'plugins' group in your Gemfile. You can force Vagrant to take over" - puts "with VAGRANT_FORCE_BUNDLER." - puts - end -end - -require_relative "bundler" -require_relative "plugin/manager" - -plugins = Vagrant::Plugin::Manager.instance.installed_plugins -Vagrant::Bundler.instance.init!(plugins) - -ENV["VAGRANT_INTERNAL_BUNDLERIZED"] = "1" - -# If the VAGRANT_EXECUTABLE env is set, then we use that to point to a -# Ruby file to directly execute. Otherwise, we just depend on PATH lookup. -# This minor optimization can save hundreds of milliseconds on Windows. -if ENV["VAGRANT_EXECUTABLE"] - Kernel.exec("ruby", ENV["VAGRANT_EXECUTABLE"], *ARGV) -else - Kernel.exec("vagrant", *ARGV) -end diff --git a/lib/vagrant/shared_helpers.rb b/lib/vagrant/shared_helpers.rb index fe114013e..ffe61e571 100644 --- a/lib/vagrant/shared_helpers.rb +++ b/lib/vagrant/shared_helpers.rb @@ -38,11 +38,18 @@ module Vagrant ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] end + # Should the plugin system be initialized + # + # @return [Boolean] + def self.plugins_init? + !ENV['VAGRANT_DISABLE_PLUGIN_INIT'] + end + # This returns whether or not 3rd party plugins should and can be loaded. # # @return [Boolean] def self.plugins_enabled? - !ENV["VAGRANT_NO_PLUGINS"] && $vagrant_bundler_runtime + !ENV["VAGRANT_NO_PLUGINS"] end # Whether or not super quiet mode is enabled. This is ill-advised. diff --git a/lib/vagrant/util.rb b/lib/vagrant/util.rb index 896b5d7e0..07f3b1802 100644 --- a/lib/vagrant/util.rb +++ b/lib/vagrant/util.rb @@ -9,6 +9,7 @@ module Vagrant autoload :SafeExec, 'vagrant/util/safe_exec' autoload :StackedProcRunner, 'vagrant/util/stacked_proc_runner' autoload :TemplateRenderer, 'vagrant/util/template_renderer' + autoload :StringBlockEditor, 'vagrant/util/string_block_editor' autoload :Subprocess, 'vagrant/util/subprocess' end end diff --git a/lib/vagrant/util/downloader.rb b/lib/vagrant/util/downloader.rb index 03e623897..b327b5d2f 100644 --- a/lib/vagrant/util/downloader.rb +++ b/lib/vagrant/util/downloader.rb @@ -1,7 +1,8 @@ require "uri" require "log4r" - +require "digest/md5" +require "digest/sha1" require "vagrant/util/busy" require "vagrant/util/platform" require "vagrant/util/subprocess" @@ -18,6 +19,12 @@ module Vagrant # Vagrant/1.7.4 (+https://www.vagrantup.com; ruby2.1.0) USER_AGENT = "Vagrant/#{VERSION} (+https://www.vagrantup.com; #{RUBY_ENGINE}#{RUBY_VERSION})".freeze + # Supported file checksum + CHECKSUM_MAP = { + :md5 => Digest::MD5, + :sha1 => Digest::SHA1 + }.freeze + attr_reader :source attr_reader :destination @@ -52,6 +59,10 @@ module Vagrant @ui = options[:ui] @client_cert = options[:client_cert] @location_trusted = options[:location_trusted] + @checksums = { + :md5 => options[:md5], + :sha1 => options[:sha1] + } end # This executes the actual download, downloading the source file @@ -161,6 +172,8 @@ module Vagrant end end + validate_download!(@source, @destination, @checksums) + # Everything succeeded true end @@ -178,6 +191,46 @@ module Vagrant protected + # Apply any checksum validations based on provided + # options content + # + # @param source [String] Source of file + # @param path [String, Pathname] local file path + # @param checksums [Hash] User provided options + # @option checksums [String] :md5 Compare MD5 checksum + # @option checksums [String] :sha1 Compare SHA1 checksum + # @return [Boolean] + def validate_download!(source, path, checksums) + CHECKSUM_MAP.each do |type, klass| + if checksums[type] + result = checksum_file(klass, path) + @logger.debug("Validating checksum (#{type}) for #{source}. " \ + "expected: #{checksums[type]} actual: #{result}") + if checksums[type] != result + raise Errors::DownloaderChecksumError.new( + source: source, + path: path, + type: type, + expected_checksum: checksums[type], + actual_checksum: result + ) + end + end + end + true + end + + # Generate checksum on given file + # + # @param digest_class [Class] Digest class to use for generating checksum + # @param path [String, Pathname] Path to file + # @return [String] hexdigest result + def checksum_file(digest_class, path) + digester = digest_class.new + digester.file(path) + digester.hexdigest + end + def execute_curl(options, subprocess_options, &data_proc) options = options.dup options << subprocess_options diff --git a/lib/vagrant/util/env.rb b/lib/vagrant/util/env.rb index 689c9e5ac..dc8ed9f9f 100644 --- a/lib/vagrant/util/env.rb +++ b/lib/vagrant/util/env.rb @@ -1,11 +1,11 @@ -require "bundler" - module Vagrant module Util class Env def self.with_original_env original_env = ENV.to_hash - ENV.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV) + if defined?(::Bundler) && defined?(::Bundler::ORIGINAL_ENV) + ENV.replace(::Bundler::ORIGINAL_ENV) + end ENV.update(Vagrant.original_env) yield ensure @@ -37,7 +37,9 @@ module Vagrant # the block to execute with the cleaned environment def self.with_clean_env with_original_env do - ENV["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"] + if ENV["BUNDLE_ORIG_MANPATH"] + ENV["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"] + end ENV.delete_if { |k,_| k[0,7] == "BUNDLE_" } if ENV.has_key? "RUBYOPT" ENV["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "") diff --git a/lib/vagrant/util/safe_exec.rb b/lib/vagrant/util/safe_exec.rb index d49a58a56..37e6a38c3 100644 --- a/lib/vagrant/util/safe_exec.rb +++ b/lib/vagrant/util/safe_exec.rb @@ -7,6 +7,9 @@ module Vagrant # thread. In that case, `safe_exec` automatically falls back to # forking. class SafeExec + + @@logger = Log4r::Logger.new("vagrant::util::safe_exec") + def self.exec(command, *args) # Create a list of things to rescue from. Since this is OS # specific, we need to do some defined? checks here to make @@ -18,10 +21,27 @@ module Vagrant fork_instead = false begin - pid = nil - pid = fork if fork_instead - Kernel.exec(command, *args) if pid.nil? - Process.wait(pid) if pid + if fork_instead + if Vagrant::Util::Platform.windows? + @@logger.debug("Using subprocess because windows platform") + args = args.dup << {notify: [:stdout, :stderr]} + result = Vagrant::Util::Subprocess.execute(command, *args) do |type, data| + case type + when :stdout + @@logger.info(data, new_line: false) + when :stderr + @@logger.info(data, new_line: false) + end + end + Kernel.exit(result.exit_code) + else + pid = fork + Kernel.exec(command, *args) + Process.wait(pid) + end + else + Kernel.exec(command, *args) + end rescue *rescue_from # We retried already, raise the issue and be done raise if fork_instead diff --git a/lib/vagrant/util/subprocess.rb b/lib/vagrant/util/subprocess.rb index d69286b33..3737d3897 100644 --- a/lib/vagrant/util/subprocess.rb +++ b/lib/vagrant/util/subprocess.rb @@ -144,10 +144,11 @@ module Vagrant # Record the start time for timeout purposes start_time = Time.now.to_i + open_readers = [stdout, stderr] + open_writers = notify_stdin ? [process.io.stdin] : [] @logger.debug("Selecting on IO") while true - writers = notify_stdin ? [process.io.stdin] : [] - results = ::IO.select([stdout, stderr], writers, nil, 0.1) + results = ::IO.select(open_readers, open_writers, nil, 0.1) results ||= [] readers = results[0] writers = results[1] @@ -178,8 +179,14 @@ module Vagrant break if process.exited? # Check the writers to see if they're ready, and notify any listeners - if writers && !writers.empty? - yield :stdin, process.io.stdin if block_given? + if writers && !writers.empty? && block_given? + yield :stdin, process.io.stdin + + # if the callback closed stdin, we should remove it, because + # IO.select() will throw if called with a closed io. + if process.io.stdin.closed? + open_writers = [] + end end end @@ -290,7 +297,9 @@ module Vagrant def jailbreak(env = {}) return if ENV.key?("VAGRANT_SKIP_SUBPROCESS_JAILBREAK") - env.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV) + if defined?(::Bundler) && defined?(::Bundler::ORIGINAL_ENV) + env.replace(::Bundler::ORIGINAL_ENV) + end env.merge!(Vagrant.original_env) # Bundler does this, so I guess we should as well, since I think it diff --git a/plugins/commands/box/command/prune.rb b/plugins/commands/box/command/prune.rb new file mode 100644 index 000000000..6a67b5458 --- /dev/null +++ b/plugins/commands/box/command/prune.rb @@ -0,0 +1,128 @@ +require 'optparse' + +module VagrantPlugins + module CommandBox + module Command + class Prune < Vagrant.plugin("2", :command) + def execute + options = {} + options[:force] = false + options[:dry_run] = false + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant box prune [options]" + o.separator "" + o.separator "Options:" + o.separator "" + + o.on("-p PROVIDER", "--provider PROVIDER", String, "The specific provider type for the boxes to destroy.") do |p| + options[:provider] = p + end + + o.on("-n", "--dry-run", "Only print the boxes that would be removed.") do |f| + options[:dry_run] = f + end + + o.on("--name NAME", String, "The specific box name to check for outdated versions.") do |name| + options[:name] = name + end + + o.on("-f", "--force", "Destroy without confirmation even when box is in use.") do |f| + options[:force] = f + end + end + + # Parse the options + argv = parse_options(opts) + return if !argv + + boxes = @env.boxes.all.sort + if boxes.empty? + return @env.ui.warn(I18n.t("vagrant.commands.box.no_installed_boxes"), prefix: false) + end + + delete_oldest_boxes(boxes, options[:provider], options[:force], options[:name], options[:dry_run]) + + # Success, exit status 0 + 0 + end + + private + + def delete_oldest_boxes(boxes, only_provider, skip_confirm, only_name, dry_run) + # Find the longest box name + longest_box = boxes.max_by { |x| x[0].length } + longest_box_length = longest_box[0].length + + # Hash map to keep track of newest versions + newest_boxes = Hash.new + + # First find the newest version for every installed box + boxes.each do |name, version, provider| + next if only_provider and only_provider != provider.to_s + next if only_name and only_name != name + + # Nested to make sure it works for boxes with different providers + if newest_boxes.has_key?(name) + if newest_boxes[name].has_key?(provider) + saved = Gem::Version.new(newest_boxes[name][provider]) + current = Gem::Version.new(version) + if current > saved + newest_boxes[name][provider] = version + end + else + newest_boxes[name][provider] = version + end + else + newest_boxes[name] = Hash.new + newest_boxes[name][provider] = version + end + end + + @env.ui.info("The following boxes will be kept..."); + newest_boxes.each do |name, providers| + providers.each do |provider, version| + @env.ui.info("#{name.ljust(longest_box_length)} (#{provider}, #{version})") + + @env.ui.machine("box-name", name) + @env.ui.machine("box-provider", provider) + @env.ui.machine("box-version", version) + end + end + + @env.ui.info("", prefix: false) + @env.ui.info("Checking for older boxes..."); + + # Track if we removed anything so the user can be informed + removed_any_box = false + boxes.each do |name, version, provider| + next if !newest_boxes.has_key?(name) or !newest_boxes[name].has_key?(provider) + + current = Gem::Version.new(version) + saved = Gem::Version.new(newest_boxes[name][provider]) + if current < saved + removed_any_box = true + + # Use the remove box action + if dry_run + @env.ui.info("Would remove #{name} #{provider} #{version}") + else + @env.action_runner.run(Vagrant::Action.action_box_remove, { + box_name: name, + box_provider: provider, + box_version: version, + force_confirm_box_remove: skip_confirm, + box_remove_all_versions: false, + }) + end + end + end + + if !removed_any_box + @env.ui.info("No old versions of boxes to remove..."); + end + end + end + end + end +end diff --git a/plugins/commands/box/command/root.rb b/plugins/commands/box/command/root.rb index 1749d9bb7..79930ffb9 100644 --- a/plugins/commands/box/command/root.rb +++ b/plugins/commands/box/command/root.rb @@ -34,6 +34,11 @@ module VagrantPlugins Remove end + @subcommands.register(:prune) do + require_relative "prune" + Prune + end + @subcommands.register(:repackage) do require File.expand_path("../repackage", __FILE__) Repackage diff --git a/plugins/commands/plugin/action.rb b/plugins/commands/plugin/action.rb index 832bc3508..e1ed6dbd7 100644 --- a/plugins/commands/plugin/action.rb +++ b/plugins/commands/plugin/action.rb @@ -6,6 +6,12 @@ module VagrantPlugins module CommandPlugin module Action # This middleware sequence will install a plugin. + def self.action_expunge + Vagrant::Action::Builder.new.tap do |b| + b.use ExpungePlugins + end + end + def self.action_install Vagrant::Action::Builder.new.tap do |b| b.use InstallGem @@ -27,6 +33,13 @@ module VagrantPlugins end end + # This middleware sequence will repair installed plugins. + def self.action_repair + Vagrant::Action::Builder.new.tap do |b| + b.use RepairPlugins + end + end + # This middleware sequence will uninstall a plugin. def self.action_uninstall Vagrant::Action::Builder.new.tap do |b| @@ -44,10 +57,12 @@ module VagrantPlugins # The autoload farm action_root = Pathname.new(File.expand_path("../action", __FILE__)) + autoload :ExpungePlugins, action_root.join("expunge_plugins") autoload :InstallGem, action_root.join("install_gem") autoload :LicensePlugin, action_root.join("license_plugin") autoload :ListPlugins, action_root.join("list_plugins") autoload :PluginExistsCheck, action_root.join("plugin_exists_check") + autoload :RepairPlugins, action_root.join("repair_plugins") autoload :UninstallPlugin, action_root.join("uninstall_plugin") autoload :UpdateGems, action_root.join("update_gems") end diff --git a/plugins/commands/plugin/action/expunge_plugins.rb b/plugins/commands/plugin/action/expunge_plugins.rb new file mode 100644 index 000000000..0debc5803 --- /dev/null +++ b/plugins/commands/plugin/action/expunge_plugins.rb @@ -0,0 +1,51 @@ +require "vagrant/plugin/manager" + +module VagrantPlugins + module CommandPlugin + module Action + # This middleware removes user installed plugins by + # removing: + # * ~/.vagrant.d/plugins.json + # * ~/.vagrant.d/gems + # Usage should be restricted to when a repair is + # unsuccessful and the only reasonable option remaining + # is to re-install all plugins + class ExpungePlugins + def initialize(app, env) + @app = app + end + + def call(env) + if !env[:force] + result = env[:ui].ask( + I18n.t("vagrant.commands.plugin.expunge_confirm") + + " [Y/N]:" + ) + if result.to_s.downcase.strip != 'y' + abort_action = true + end + end + + if !abort_action + plugins_json = File.join(env[:home_path], "plugins.json") + plugins_gems = env[:gems_path] + + if File.exist?(plugins_json) + FileUtils.rm(plugins_json) + end + + if File.directory?(plugins_gems) + FileUtils.rm_rf(plugins_gems) + end + + env[:ui].info(I18n.t("vagrant.commands.plugin.expunge_complete")) + + @app.call(env) + else + env[:ui].info(I18n.t("vagrant.commands.plugin.expunge_aborted")) + end + end + end + end + end +end diff --git a/plugins/commands/plugin/action/list_plugins.rb b/plugins/commands/plugin/action/list_plugins.rb index 37b168867..f5f8d41bf 100644 --- a/plugins/commands/plugin/action/list_plugins.rb +++ b/plugins/commands/plugin/action/list_plugins.rb @@ -18,7 +18,11 @@ module VagrantPlugins def call(env) manager = Vagrant::Plugin::Manager.instance plugins = manager.installed_plugins - specs = manager.installed_specs + specs = Hash[ + manager.installed_specs.map do |spec| + [spec.name, spec] + end + ] # Output! if specs.empty? @@ -26,9 +30,10 @@ module VagrantPlugins return @app.call(env) end - specs.each do |spec| - # Grab the plugin. - plugin = plugins[spec.name] + plugins.each do |plugin_name, plugin| + + spec = specs[plugin_name] + next if spec.nil? system = "" system = ", system" if plugin && plugin["system"] diff --git a/plugins/commands/plugin/action/repair_plugins.rb b/plugins/commands/plugin/action/repair_plugins.rb new file mode 100644 index 000000000..9745d378b --- /dev/null +++ b/plugins/commands/plugin/action/repair_plugins.rb @@ -0,0 +1,39 @@ +require "vagrant/plugin/manager" + +module VagrantPlugins + module CommandPlugin + module Action + # This middleware attempts to repair installed plugins. + # + # In general, if plugins are failing to properly load the + # core issue will likely be one of two issues: + # 1. manual modifications within ~/.vagrant.d/ + # 2. vagrant upgrade + # Running an install on configured plugin set will most + # likely fix these issues, which is all this action does. + class RepairPlugins + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::plugins::plugincommand::repair") + end + + def call(env) + env[:ui].info(I18n.t("vagrant.commands.plugin.repairing")) + plugins = Vagrant::Plugin::Manager.instance.installed_plugins + begin + Vagrant::Bundler.instance.init!(plugins, :repair) + env[:ui].info(I18n.t("vagrant.commands.plugin.repair_complete")) + rescue Exception => e + @logger.error("Failed to repair user installed plugins: #{e.class} - #{e}") + e.backtrace.each do |backtrace_line| + @logger.debug(backtrace_line) + end + env[:ui].error(I18n.t("vagrant.commands.plugin.repair_failed", message: e.message)) + end + # Continue + @app.call(env) + end + end + end + end +end diff --git a/plugins/commands/plugin/action/update_gems.rb b/plugins/commands/plugin/action/update_gems.rb index 57414273f..f59fce996 100644 --- a/plugins/commands/plugin/action/update_gems.rb +++ b/plugins/commands/plugin/action/update_gems.rb @@ -19,17 +19,15 @@ module VagrantPlugins end manager = Vagrant::Plugin::Manager.instance - installed_specs = manager.installed_specs + installed_plugins = manager.installed_plugins new_specs = manager.update_plugins(names) + updated_plugins = manager.installed_plugins updated = {} - installed_specs.each do |ispec| - new_specs.each do |uspec| - next if uspec.name != ispec.name - next if ispec.version >= uspec.version - next if updated[uspec.name] && updated[uspec.name].version >= uspec.version - - updated[uspec.name] = uspec + installed_plugins.each do |name, info| + update = updated_plugins[name] + if update && update["installed_gem_version"] != info["installed_gem_version"] + updated[name] = update["installed_gem_version"] end end @@ -37,9 +35,9 @@ module VagrantPlugins env[:ui].success(I18n.t("vagrant.commands.plugin.up_to_date")) end - updated.values.each do |spec| + updated.each do |name, version| env[:ui].success(I18n.t("vagrant.commands.plugin.updated", - name: spec.name, version: spec.version.to_s)) + name: name, version: version.to_s)) end # Continue diff --git a/plugins/commands/plugin/command/expunge.rb b/plugins/commands/plugin/command/expunge.rb new file mode 100644 index 000000000..720e699b0 --- /dev/null +++ b/plugins/commands/plugin/command/expunge.rb @@ -0,0 +1,67 @@ +require 'optparse' + +require_relative "base" + +module VagrantPlugins + module CommandPlugin + module Command + class Expunge < Base + def execute + options = {} + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant plugin expunge [-h]" + + o.on("--force", "Do not prompt for confirmation") do |force| + options[:force] = force + end + + o.on("--reinstall", "Reinstall current plugins after expunge") do |reinstall| + options[:reinstall] = reinstall + end + end + + # Parse the options + argv = parse_options(opts) + return if !argv + raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length > 0 + + plugins = Vagrant::Plugin::Manager.instance.installed_plugins + + if !options[:reinstall] && !options[:force] && !plugins.empty? + result = @env.ui.ask( + I18n.t("vagrant.commands.plugin.expunge_request_reinstall") + + " [Y/N]:" + ) + options[:reinstall] = result.to_s.downcase.strip == "y" + end + + # Remove all installed user plugins + action(Action.action_expunge, options) + + if options[:reinstall] + @env.ui.info(I18n.t("vagrant.commands.plugin.expunge_reinstall")) + plugins.each do |plugin_name, plugin_info| + next if plugin_info["system"] # system plugins do not require re-install + # Rebuild information hash to use symbols + plugin_info = Hash[ + plugin_info.map do |key, value| + ["plugin_#{key}".to_sym, value] + end + ] + action( + Action.action_install, + plugin_info.merge( + plugin_name: plugin_name + ) + ) + end + end + + # Success, exit status 0 + 0 + end + end + end + end +end diff --git a/plugins/commands/plugin/command/mixin_install_opts.rb b/plugins/commands/plugin/command/mixin_install_opts.rb index 4846883e1..517b5158b 100644 --- a/plugins/commands/plugin/command/mixin_install_opts.rb +++ b/plugins/commands/plugin/command/mixin_install_opts.rb @@ -13,15 +13,6 @@ module VagrantPlugins options[:entry_point] = entry_point end - # @deprecated - o.on("--plugin-prerelease", - "Allow prerelease versions of this plugin.") do |plugin_prerelease| - puts "--plugin-prelease is deprecated and will be removed in the next" - puts "version of Vagrant. It has no effect now. Use the '--plugin-version'" - puts "flag to get a specific pre-release version." - puts - end - o.on("--plugin-clean-sources", "Remove all plugin sources defined so far (including defaults)") do |clean| options[:plugin_sources] = [] if clean diff --git a/plugins/commands/plugin/command/repair.rb b/plugins/commands/plugin/command/repair.rb new file mode 100644 index 000000000..6deff6ae2 --- /dev/null +++ b/plugins/commands/plugin/command/repair.rb @@ -0,0 +1,28 @@ +require 'optparse' + +require_relative "base" + +module VagrantPlugins + module CommandPlugin + module Command + class Repair < Base + def execute + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant plugin repair [-h]" + end + + # Parse the options + argv = parse_options(opts) + return if !argv + raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp if argv.length > 0 + + # Attempt to repair installed plugins + action(Action.action_repair) + + # Success, exit status 0 + 0 + end + end + end + end +end diff --git a/plugins/commands/plugin/command/root.rb b/plugins/commands/plugin/command/root.rb index 8c4d1ad1b..6953eb495 100644 --- a/plugins/commands/plugin/command/root.rb +++ b/plugins/commands/plugin/command/root.rb @@ -14,6 +14,11 @@ module VagrantPlugins @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) @subcommands = Vagrant::Registry.new + @subcommands.register(:expunge) do + require_relative "expunge" + Expunge + end + @subcommands.register(:install) do require_relative "install" Install @@ -29,6 +34,11 @@ module VagrantPlugins List end + @subcommands.register(:repair) do + require_relative "repair" + Repair + end + @subcommands.register(:update) do require_relative "update" Update diff --git a/plugins/communicators/ssh/communicator.rb b/plugins/communicators/ssh/communicator.rb index 0648e3cdb..4c3eb40ac 100644 --- a/plugins/communicators/ssh/communicator.rb +++ b/plugins/communicators/ssh/communicator.rb @@ -540,7 +540,7 @@ module VagrantPlugins end # Set the terminal - ch2.send_data "export TERM=vt100\n" + ch2.send_data generate_environment_export("TERM", "vt100") # Set SSH_AUTH_SOCK if we are in sudo and forwarding agent. # This is to work around often misconfigured boxes where @@ -563,7 +563,7 @@ module VagrantPlugins @logger.warn("No SSH_AUTH_SOCK found despite forward_agent being set.") else @logger.info("Setting SSH_AUTH_SOCK remotely: #{auth_socket}") - ch2.send_data "export SSH_AUTH_SOCK=#{auth_socket}\n" + ch2.send_data generate_environment_export("SSH_AUTH_SOCK", auth_socket) end end @@ -572,9 +572,9 @@ module VagrantPlugins # without the cruft added from pty mode. if pty data = "stty raw -echo\n" - data += "export PS1=\n" - data += "export PS2=\n" - data += "export PROMPT_COMMAND=\n" + data += generate_environment_export("PS1", "") + data += generate_environment_export("PS2", "") + data += generate_environment_export("PROMPT_COMMAND", "") data += "printf #{PTY_DELIM_START}\n" data += "#{command}\n" data += "exitcode=$?\n" @@ -670,6 +670,11 @@ module VagrantPlugins source_path = Vagrant.source_root.join("keys", "vagrant") return File.read(path).chomp == source_path.read.chomp end + + def generate_environment_export(env_key, env_value) + template = @machine.config.ssh.export_command_template + template.sub("%ENV_KEY%", env_key).sub("%ENV_VALUE%", env_value) + "\n" + end end end end diff --git a/plugins/communicators/winrm/communicator.rb b/plugins/communicators/winrm/communicator.rb index 78ba73879..8c7a292cf 100644 --- a/plugins/communicators/winrm/communicator.rb +++ b/plugins/communicators/winrm/communicator.rb @@ -104,7 +104,7 @@ module VagrantPlugins @logger.info("Checking whether WinRM is ready...") result = Timeout.timeout(@machine.config.winrm.timeout) do - shell(true).powershell("hostname") + shell(true).cmd("hostname") end @logger.info("WinRM is ready!") diff --git a/plugins/communicators/winrm/shell.rb b/plugins/communicators/winrm/shell.rb index f55c8fb75..0ea9e3cbb 100644 --- a/plugins/communicators/winrm/shell.rb +++ b/plugins/communicators/winrm/shell.rb @@ -56,11 +56,15 @@ module VagrantPlugins def powershell(command, &block) # Ensure an exit code command += "\r\nif ($?) { exit 0 } else { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }" - execute_with_rescue(executor.method("run_powershell_script"), command, &block) + session.create_executor do |executor| + execute_with_rescue(executor.method("run_powershell_script"), command, &block) + end end def cmd(command, &block) - execute_with_rescue(executor.method("run_cmd"), command, &block) + session.create_executor do |executor| + execute_with_rescue(executor.method("run_cmd"), command, &block) + end end def wql(query, &block) @@ -172,10 +176,6 @@ module VagrantPlugins @session ||= new_session end - def executor - @executor ||= session.create_executor - end - def endpoint case @config.transport.to_sym when :ssl diff --git a/plugins/guests/arch/cap/change_host_name.rb b/plugins/guests/arch/cap/change_host_name.rb index 0b10112e1..39bc5e586 100644 --- a/plugins/guests/arch/cap/change_host_name.rb +++ b/plugins/guests/arch/cap/change_host_name.rb @@ -8,18 +8,16 @@ module VagrantPlugins if !comm.test("hostname -f | grep '^#{name}$'", sudo: false) basename = name.split(".", 2)[0] comm.sudo <<-EOH.gsub(/^ {14}/, "") - set -e + # Remove comments and blank lines from /etc/hosts + sed -i'' -e 's/#.*$//' -e '/^$/d' /etc/hosts # Set hostname hostnamectl set-hostname '#{basename}' - # Remove comments and blank lines from /etc/hosts - sed -i'' -e 's/#.*$//' -e '/^$/d' /etc/hosts - # Prepend ourselves to /etc/hosts - grep -w '#{name}' /etc/hosts || { + test $? -eq 0 && (grep -w '#{name}' /etc/hosts || { sed -i'' '1i 127.0.0.1\\t#{name}\\t#{basename}' /etc/hosts - } + }) EOH end end diff --git a/plugins/guests/arch/cap/configure_networks.rb b/plugins/guests/arch/cap/configure_networks.rb index 2fa9c8017..0c748b3d8 100644 --- a/plugins/guests/arch/cap/configure_networks.rb +++ b/plugins/guests/arch/cap/configure_networks.rb @@ -12,10 +12,9 @@ module VagrantPlugins def self.configure_networks(machine, networks) comm = machine.communicate + commands = [] - commands = ["set -e"] interfaces = machine.guest.capability(:network_interfaces) - networks.each.with_index do |network, i| network[:device] = interfaces[network[:interface]] @@ -42,15 +41,15 @@ module VagrantPlugins commands << <<-EOH.gsub(/^ {14}/, '') # Configure #{network[:device]} - mv '#{remote_path}' '/etc/netctl/#{network[:device]}' - ip link set '#{network[:device]}' down - netctl restart '#{network[:device]}' + mv '#{remote_path}' '/etc/netctl/#{network[:device]}' && + ip link set '#{network[:device]}' down && + netctl restart '#{network[:device]}' && netctl enable '#{network[:device]}' EOH end # Run all the network modification commands in one communicator call. - comm.sudo(commands.join("\n")) + comm.sudo(commands.join(" && \n")) end end end diff --git a/plugins/guests/arch/cap/nfs.rb b/plugins/guests/arch/cap/nfs.rb index 116256ed2..5ac5dfe67 100644 --- a/plugins/guests/arch/cap/nfs.rb +++ b/plugins/guests/arch/cap/nfs.rb @@ -15,8 +15,7 @@ module VagrantPlugins # https://bbs.archlinux.org/viewtopic.php?id=193410 # comm.sudo <<-EOH.gsub(/^ {12}/, "") - set -e - systemctl enable rpcbind + systemctl enable rpcbind && systemctl start rpcbind EOH end @@ -24,8 +23,7 @@ module VagrantPlugins def self.nfs_client_install(machine) comm = machine.communicate comm.sudo <<-EOH.gsub(/^ {12}/, "") - set -e - pacman --noconfirm -Syy + pacman --noconfirm -Syy && pacman --noconfirm -S nfs-utils ntp EOH end diff --git a/plugins/guests/arch/cap/rsync.rb b/plugins/guests/arch/cap/rsync.rb index 99aefa208..991a19478 100644 --- a/plugins/guests/arch/cap/rsync.rb +++ b/plugins/guests/arch/cap/rsync.rb @@ -5,9 +5,9 @@ module VagrantPlugins def self.rsync_install(machine) comm = machine.communicate comm.sudo <<-EOH.gsub(/^ {12}/, '') - set -e pacman -Sy --noconfirm pacman -S --noconfirm rsync + exit $? EOH end end diff --git a/plugins/guests/atomic/cap/change_host_name.rb b/plugins/guests/atomic/cap/change_host_name.rb index 2c501c8dd..1833cf32a 100644 --- a/plugins/guests/atomic/cap/change_host_name.rb +++ b/plugins/guests/atomic/cap/change_host_name.rb @@ -8,18 +8,16 @@ module VagrantPlugins if !comm.test("hostname -f | grep '^#{name}$'", sudo: false) basename = name.split(".", 2)[0] comm.sudo <<-EOH.gsub(/^ {14}/, "") - set -e + # Remove comments and blank lines from /etc/hosts + sed -i'' -e 's/#.*$//' -e '/^$/d' /etc/hosts # Set hostname hostnamectl set-hostname '#{basename}' - # Remove comments and blank lines from /etc/hosts - sed -i'' -e 's/#.*$//' -e '/^$/d' /etc/hosts - # Prepend ourselves to /etc/hosts - grep -w '#{name}' /etc/hosts || { + test $? -eq 0 && (grep -w '#{name}' /etc/hosts || { sed -i'' '1i 127.0.0.1\\t#{name}\\t#{basename}' /etc/hosts - } + }) EOH end end diff --git a/plugins/guests/bsd/cap/nfs.rb b/plugins/guests/bsd/cap/nfs.rb index a54835e89..458ebcfbe 100644 --- a/plugins/guests/bsd/cap/nfs.rb +++ b/plugins/guests/bsd/cap/nfs.rb @@ -11,10 +11,8 @@ module VagrantPlugins def self.mount_nfs_folder(machine, ip, folders) comm = machine.communicate + # Mount each folder separately so we can retry. folders.each do |name, opts| - # Mount each folder separately so we can retry. - commands = ["set -e"] - # Shellescape the paths in case they do not have special characters. guest_path = Shellwords.escape(opts[:guestpath]) host_path = Shellwords.escape(opts[:hostpath]) @@ -29,14 +27,14 @@ module VagrantPlugins mount_opts = mount_opts.join(",") # Make the directory on the guest. - commands << "mkdir -p #{guest_path}" + machine.communicate.sudo("mkdir -p #{guest_path}") # Perform the mount operation. - commands << "/sbin/mount -t nfs -o '#{mount_opts}' #{ip}:#{host_path} #{guest_path}" + command = "/sbin/mount -t nfs -o '#{mount_opts}' #{ip}:#{host_path} #{guest_path}" # Run the command, raising a specific error. retryable(on: Vagrant::Errors::NFSMountFailed, tries: 3, sleep: 5) do - machine.communicate.sudo(commands.join("\n"), + machine.communicate.sudo(command, error_class: Vagrant::Errors::NFSMountFailed, shell: "sh", ) diff --git a/plugins/guests/bsd/cap/public_key.rb b/plugins/guests/bsd/cap/public_key.rb index 8e84c0204..4f25aa967 100644 --- a/plugins/guests/bsd/cap/public_key.rb +++ b/plugins/guests/bsd/cap/public_key.rb @@ -22,14 +22,13 @@ module VagrantPlugins # Use execute (not sudo) because we want to execute this as the SSH # user (which is "vagrant" by default). comm.execute <<-EOH.gsub(/^ {12}/, "") - set -e - mkdir -p ~/.ssh - chmod 0700 ~/.ssh - cat '#{remote_path}' >> ~/.ssh/authorized_keys - chmod 0600 ~/.ssh/authorized_keys - + chmod 0700 ~/.ssh && + cat '#{remote_path}' >> ~/.ssh/authorized_keys && + chmod 0600 ~/.ssh/authorized_keys + result=$? rm -f '#{remote_path}' + exit $result EOH end @@ -49,15 +48,16 @@ module VagrantPlugins # Use execute (not sudo) because we want to execute this as the SSH # user (which is "vagrant" by default). comm.execute <<-EOH.sub(/^ {12}/, "") - set -e - + result=0 if test -f ~/.ssh/authorized_keys; then - grep -v -x -f '#{remote_path}' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp - mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys - chmod 0600 ~/.ssh/authorized_keys + grep -v -x -f '#{remote_path}' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp && + mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys && + chmod 0600 ~/.ssh/authorized_keys + result=$? fi rm -f '#{remote_path}' + exit $result EOH end end diff --git a/plugins/guests/darwin/cap/change_host_name.rb b/plugins/guests/darwin/cap/change_host_name.rb index 5d5902d9e..03bc70840 100644 --- a/plugins/guests/darwin/cap/change_host_name.rb +++ b/plugins/guests/darwin/cap/change_host_name.rb @@ -8,16 +8,17 @@ module VagrantPlugins if !comm.test("hostname -f | grep '^#{name}$'", sudo: false) basename = name.split(".", 2)[0] - comm.sudo <<-EOH.gsub(/^ {14}/, '') - set -e - + # LocalHostName should not contain dots - it is used by Bonjour and + # visible through file sharing services. + comm.sudo <<-EOH.gsub(/^ */, '') # Set hostname - scutil --set ComputerName '#{name}' - scutil --set HostName '#{name}' - - # LocalHostName should not contain dots - it is used by Bonjour and - # visible through file sharing services. - scutil --set LocalHostName '#{basename}' + scutil --set ComputerName '#{name}' && + scutil --set HostName '#{name}' && + scutil --set LocalHostName '#{basename}' + result=$? + if [ $result -ne 0 ]; then + exit $result + fi hostname '#{name}' @@ -27,8 +28,8 @@ module VagrantPlugins # Prepend ourselves to /etc/hosts - sed on bsd is sad grep -w '#{name}' /etc/hosts || { - echo -e '127.0.0.1\\t#{name}\\t#{basename}' | cat - /etc/hosts > /tmp/tmp-hosts - mv /tmp/tmp-hosts /etc/hosts + echo -e '127.0.0.1\\t#{name}\\t#{basename}' | cat - /etc/hosts > /tmp/tmp-hosts && + mv /tmp/tmp-hosts /etc/hosts } EOH end diff --git a/plugins/guests/debian/cap/change_host_name.rb b/plugins/guests/debian/cap/change_host_name.rb index a1ce9195a..e496004ad 100644 --- a/plugins/guests/debian/cap/change_host_name.rb +++ b/plugins/guests/debian/cap/change_host_name.rb @@ -8,9 +8,6 @@ module VagrantPlugins if !comm.test("hostname -f | grep '^#{name}$'", sudo: false) basename = name.split(".", 2)[0] comm.sudo <<-EOH.gsub(/^ {14}/, '') - # Ensure exit on command error - set -e - # Set the hostname echo '#{basename}' > /etc/hostname hostname -F /etc/hostname diff --git a/plugins/guests/debian/cap/configure_networks.rb b/plugins/guests/debian/cap/configure_networks.rb index 2553516a8..263fe924a 100644 --- a/plugins/guests/debian/cap/configure_networks.rb +++ b/plugins/guests/debian/cap/configure_networks.rb @@ -11,7 +11,7 @@ module VagrantPlugins def self.configure_networks(machine, networks) comm = machine.communicate - commands = ["set -e"] + commands = [] entries = [] interfaces = machine.guest.capability(:network_interfaces) diff --git a/plugins/guests/debian/cap/nfs.rb b/plugins/guests/debian/cap/nfs.rb index 881469906..de9da6667 100644 --- a/plugins/guests/debian/cap/nfs.rb +++ b/plugins/guests/debian/cap/nfs.rb @@ -5,9 +5,9 @@ module VagrantPlugins def self.nfs_client_install(machine) comm = machine.communicate comm.sudo <<-EOH.gsub(/^ {12}/, '') - set -e apt-get -yqq update apt-get -yqq install nfs-common portmap + exit $? EOH end end diff --git a/plugins/guests/debian/cap/rsync.rb b/plugins/guests/debian/cap/rsync.rb index b503f8cf3..6fca5d4f1 100644 --- a/plugins/guests/debian/cap/rsync.rb +++ b/plugins/guests/debian/cap/rsync.rb @@ -5,7 +5,6 @@ module VagrantPlugins def self.rsync_install(machine) comm = machine.communicate comm.sudo <<-EOH.gsub(/^ {14}/, '') - set -e apt-get -yqq update apt-get -yqq install rsync EOH diff --git a/plugins/guests/debian/guest.rb b/plugins/guests/debian/guest.rb index 6631bda6c..9fb54c670 100644 --- a/plugins/guests/debian/guest.rb +++ b/plugins/guests/debian/guest.rb @@ -1,11 +1,10 @@ -require "vagrant" +require_relative '../linux/guest' module VagrantPlugins module GuestDebian - class Guest < Vagrant.plugin("2", :guest) - def detect?(machine) - machine.communicate.test("cat /etc/issue | grep 'Debian'") - end + class Guest < VagrantPlugins::GuestLinux::Guest + # Name used for guest detection + GUEST_DETECTION_NAME = "debian".freeze end end end diff --git a/plugins/guests/gentoo/cap/change_host_name.rb b/plugins/guests/gentoo/cap/change_host_name.rb index 7d8ca3ac5..36377e077 100644 --- a/plugins/guests/gentoo/cap/change_host_name.rb +++ b/plugins/guests/gentoo/cap/change_host_name.rb @@ -8,8 +8,6 @@ module VagrantPlugins if !comm.test("hostname -f | grep '^#{name}$'", sudo: false) basename = name.split(".", 2)[0] comm.sudo <<-EOH.gsub(/^ {14}/, "") - set -e - # Set the hostname hostname '#{basename}' echo "hostname=#{basename}" > /etc/conf.d/hostname @@ -20,8 +18,8 @@ module VagrantPlugins # Prepend ourselves to /etc/hosts grep -w '#{name}' /etc/hosts || { - echo -e '127.0.0.1\\t#{name}\\t#{basename}' | cat - /etc/hosts > /tmp/tmp-hosts - mv /tmp/tmp-hosts /etc/hosts + echo -e '127.0.0.1\\t#{name}\\t#{basename}' | cat - /etc/hosts > /tmp/tmp-hosts && + mv /tmp/tmp-hosts /etc/hosts } EOH end diff --git a/plugins/guests/linux/cap/mount_smb_shared_folder.rb b/plugins/guests/linux/cap/mount_smb_shared_folder.rb index 867a2ddc4..bbec27148 100644 --- a/plugins/guests/linux/cap/mount_smb_shared_folder.rb +++ b/plugins/guests/linux/cap/mount_smb_shared_folder.rb @@ -94,7 +94,7 @@ SCRIPT # Emit an upstart event if we can machine.communicate.sudo <<-SCRIPT -if command -v /sbin/init && /sbin/init --version | grep upstart; then +if command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT='#{expanded_guest_path}' fi SCRIPT diff --git a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb index eb9640bf5..4f285cff8 100644 --- a/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb +++ b/plugins/guests/linux/cap/mount_virtualbox_shared_folder.rb @@ -89,7 +89,7 @@ module VagrantPlugins # Emit an upstart event if we can machine.communicate.sudo <<-EOH.gsub(/^ {12}/, "") - if command -v /sbin/init && /sbin/init --version | grep upstart; then + if command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path} fi EOH diff --git a/plugins/guests/linux/cap/network_interfaces.rb b/plugins/guests/linux/cap/network_interfaces.rb index 8a4c64dd3..372c1b9c6 100644 --- a/plugins/guests/linux/cap/network_interfaces.rb +++ b/plugins/guests/linux/cap/network_interfaces.rb @@ -2,6 +2,11 @@ module VagrantPlugins module GuestLinux module Cap class NetworkInterfaces + # Valid ethernet device prefix values. + # eth - classic prefix + # en - predictable interface names prefix + POSSIBLE_ETHERNET_PREFIXES = ["eth".freeze, "en".freeze].freeze + @@logger = Log4r::Logger.new("vagrant::guest::linux::network_interfaces") # Get network interfaces as a list. The result will be something like: @@ -18,6 +23,9 @@ module VagrantPlugins @@logger.debug("Unsorted list: #{ifaces.inspect}") # Break out integers from strings and sort the arrays to provide # a natural sort for the interface names + # NOTE: Devices named with a hex value suffix will _not_ be sorted + # as expected. This is generally seen with veth* devices, and proper ordering + # is currently not required ifaces = ifaces.map do |iface| iface.scan(/(.+?)(\d+)/).flatten.map do |iface_part| if iface_part.to_i.to_s == iface_part @@ -26,8 +34,32 @@ module VagrantPlugins iface_part end end - end.sort.map(&:join) + end + ifaces = ifaces.sort do |lhs, rhs| + result = 0 + slice_length = [rhs.size, lhs.size].min + slice_length.times do |idx| + if(lhs[idx].is_a?(rhs[idx].class)) + result = lhs[idx] <=> rhs[idx] + elsif(lhs[idx].is_a?(String)) + result = 1 + else + result = -1 + end + break if result != 0 + end + result + end.map(&:join) @@logger.debug("Sorted list: #{ifaces.inspect}") + # Extract ethernet devices and place at start of list + resorted_ifaces = [] + resorted_ifaces += ifaces.find_all do |iface| + POSSIBLE_ETHERNET_PREFIXES.any?{|prefix| iface.start_with?(prefix)} && + iface.match(/^[a-zA-Z0-9]+$/) + end + resorted_ifaces += ifaces - resorted_ifaces + ifaces = resorted_ifaces + @@logger.debug("Ethernet preferred sorted list: #{ifaces.inspect}") ifaces end end diff --git a/plugins/guests/linux/cap/nfs.rb b/plugins/guests/linux/cap/nfs.rb index d47d53b98..8dadae1f3 100644 --- a/plugins/guests/linux/cap/nfs.rb +++ b/plugins/guests/linux/cap/nfs.rb @@ -13,10 +13,8 @@ module VagrantPlugins def self.mount_nfs_folder(machine, ip, folders) comm = machine.communicate + # Mount each folder separately so we can retry. folders.each do |name, opts| - # Mount each folder separately so we can retry. - commands = ["set -e"] - # Shellescape the paths in case they do not have special characters. guest_path = Shellwords.escape(opts[:guestpath]) host_path = Shellwords.escape(opts[:hostpath]) @@ -30,22 +28,24 @@ module VagrantPlugins end mount_opts = mount_opts.join(",") - # Make the directory on the guest. - commands << "mkdir -p #{guest_path}" + machine.communicate.sudo("mkdir -p #{guest_path}") - # Perform the mount operation. - commands << "mount -o #{mount_opts} #{ip}:#{host_path} #{guest_path}" - - # Emit a mount event - commands << <<-EOH.gsub(/^ {14}/, '') - if command -v /sbin/init && /sbin/init --version | grep upstart; then - /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path} + # Perform the mount operation and emit mount event if applicable + command = <<-EOH.gsub(/^ */, '') + mount -o #{mount_opts} #{ip}:#{host_path} #{guest_path} + result=$? + if test $result -eq 0; then + if test -x /sbin/initctl && command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then + /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path} + fi + else + exit $result fi EOH # Run the command, raising a specific error. retryable(on: Vagrant::Errors::NFSMountFailed, tries: 3, sleep: 5) do - machine.communicate.sudo(commands.join("\n"), + machine.communicate.sudo(command, error_class: Vagrant::Errors::NFSMountFailed, ) end diff --git a/plugins/guests/linux/cap/public_key.rb b/plugins/guests/linux/cap/public_key.rb index c75251dd3..fb8301cae 100644 --- a/plugins/guests/linux/cap/public_key.rb +++ b/plugins/guests/linux/cap/public_key.rb @@ -21,15 +21,13 @@ module VagrantPlugins # Use execute (not sudo) because we want to execute this as the SSH # user (which is "vagrant" by default). - comm.execute <<-EOH.gsub(/^ {12}/, "") - set -e - + comm.execute <<-EOH.gsub(/^ */, "") mkdir -p ~/.ssh chmod 0700 ~/.ssh - cat '#{remote_path}' >> ~/.ssh/authorized_keys - chmod 0600 ~/.ssh/authorized_keys - + cat '#{remote_path}' >> ~/.ssh/authorized_keys && chmod 0600 ~/.ssh/authorized_keys + result=$? rm -f '#{remote_path}' + exit $result EOH end @@ -48,16 +46,14 @@ module VagrantPlugins # Use execute (not sudo) because we want to execute this as the SSH # user (which is "vagrant" by default). - comm.execute <<-EOH.sub(/^ {12}/, "") - set -e - + comm.execute <<-EOH.sub(/^ */, "") if test -f ~/.ssh/authorized_keys; then grep -v -x -f '#{remote_path}' ~/.ssh/authorized_keys > ~/.ssh/authorized_keys.tmp - mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys - chmod 0600 ~/.ssh/authorized_keys + mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys && chmod 0600 ~/.ssh/authorized_keys + result=$? fi - rm -f '#{remote_path}' + exit $result EOH end end diff --git a/plugins/guests/linux/guest.rb b/plugins/guests/linux/guest.rb index 8effae8ad..d86d1daf3 100644 --- a/plugins/guests/linux/guest.rb +++ b/plugins/guests/linux/guest.rb @@ -1,8 +1,22 @@ module VagrantPlugins module GuestLinux class Guest < Vagrant.plugin("2", :guest) + # Name used for guest detection + GUEST_DETECTION_NAME = "linux".freeze + def detect?(machine) - machine.communicate.test("uname -s | grep 'Linux'") + machine.communicate.test <<-EOH.gsub(/^ */, '') + if test -r /etc/os-release; then + source /etc/os-release && test x#{self.class.const_get(:GUEST_DETECTION_NAME)} = x$ID && exit + fi + if test -x /usr/bin/lsb_release; then + /usr/bin/lsb_release -i 2>/dev/null | grep -qi #{self.class.const_get(:GUEST_DETECTION_NAME)} && exit + fi + if test -r /etc/issue; then + cat /etc/issue | grep -qi #{self.class.const_get(:GUEST_DETECTION_NAME)} && exit + fi + exit 1 + EOH end end end diff --git a/plugins/guests/mint/guest.rb b/plugins/guests/mint/guest.rb index d12e42055..b03d43c83 100644 --- a/plugins/guests/mint/guest.rb +++ b/plugins/guests/mint/guest.rb @@ -1,9 +1,10 @@ +require_relative '../linux/guest' + module VagrantPlugins module GuestMint - class Guest < Vagrant.plugin("2", :guest) - def detect?(machine) - machine.communicate.test("cat /etc/issue | grep 'Linux Mint'") - end + class Guest < VagrantPlugins::GuestLinux::Guest + # Name used for guest detection + GUEST_DETECTION_NAME = "Linux Mint".freeze end end end diff --git a/plugins/guests/netbsd/cap/change_host_name.rb b/plugins/guests/netbsd/cap/change_host_name.rb index 60a142399..226ee8d8b 100644 --- a/plugins/guests/netbsd/cap/change_host_name.rb +++ b/plugins/guests/netbsd/cap/change_host_name.rb @@ -5,9 +5,8 @@ module VagrantPlugins def self.change_host_name(machine, name) if !machine.communicate.test("hostname -s | grep '^#{name}$'") machine.communicate.sudo(< /tmp/rc.conf.vagrant_changehostname_#{name} -mv /tmp/rc.conf.vagrant_changehostname_#{name} /etc/rc.conf +sed -e 's/^hostname=.*$/hostname=#{name}/' /etc/rc.conf > /tmp/rc.conf.vagrant_changehostname_#{name} && +mv /tmp/rc.conf.vagrant_changehostname_#{name} /etc/rc.conf && hostname #{name} CMDS end diff --git a/plugins/guests/netbsd/cap/rsync.rb b/plugins/guests/netbsd/cap/rsync.rb index e88a0ceb4..9f9337645 100644 --- a/plugins/guests/netbsd/cap/rsync.rb +++ b/plugins/guests/netbsd/cap/rsync.rb @@ -8,9 +8,11 @@ module VagrantPlugins def self.rsync_install(machine) machine.communicate.sudo( - 'PKG_PATH="http://ftp.NetBSD.org/pub/pkgsrc/packages/NetBSD/' \ - '`uname -m`/`uname -r | cut -d. -f1-2`/All" ' \ - 'pkg_add rsync') + 'PATH=$PATH:/usr/sbin '\ + 'PKG_PATH="http://ftp.NetBSD.org/pub/pkgsrc/packages/NetBSD/' \ + '`uname -m`/`uname -r | cut -d. -f1-2`/All" ' \ + 'pkg_add rsync' + ) end end end diff --git a/plugins/guests/openbsd/cap/rsync.rb b/plugins/guests/openbsd/cap/rsync.rb index 54663ef6a..ac1e17a1a 100644 --- a/plugins/guests/openbsd/cap/rsync.rb +++ b/plugins/guests/openbsd/cap/rsync.rb @@ -7,10 +7,21 @@ module VagrantPlugins extend VagrantPlugins::SyncedFolderRSync::DefaultUnixCap def self.rsync_install(machine) - machine.communicate.sudo( - 'PKG_PATH="http://ftp.openbsd.org/pub/OpenBSD/' \ + install_output = {:stderr => '', :stdout => ''} + command = 'PKG_PATH="http://ftp.openbsd.org/pub/OpenBSD/' \ '`uname -r`/packages/`arch -s`/" ' \ - 'pkg_add -I rsync--') + 'pkg_add -I rsync--' + machine.communicate.sudo(command) do |type, data| + install_output[type] << data if install_output.key?(type) + end + # pkg_add returns 0 even if package was not found, so + # validate package is actually installed + machine.communicate.sudo('pkg_info -cA | grep inst:rsync-[[:digit:]]', + error_class: Vagrant::Errors::RSyncNotInstalledInGuest, + command: command, + stderr: install_output[:stderr], + stdout: install_output[:stdout] + ) end end end diff --git a/plugins/guests/photon/cap/configure_networks.rb b/plugins/guests/photon/cap/configure_networks.rb index c628fe1f5..fa51f863e 100644 --- a/plugins/guests/photon/cap/configure_networks.rb +++ b/plugins/guests/photon/cap/configure_networks.rb @@ -18,7 +18,7 @@ module VagrantPlugins device = interfaces[network[:interface]] command = "ifconfig #{device}" command << " #{network[:ip]}" if network[:ip] - command << " netmast #{network[:netmask]}" if network[:netmask] + command << " netmask #{network[:netmask]}" if network[:netmask] commands << command end diff --git a/plugins/guests/redhat/cap/change_host_name.rb b/plugins/guests/redhat/cap/change_host_name.rb index afe461d4f..e950c987b 100644 --- a/plugins/guests/redhat/cap/change_host_name.rb +++ b/plugins/guests/redhat/cap/change_host_name.rb @@ -31,8 +31,12 @@ module VagrantPlugins sed -i'' '1i 127.0.0.1\\t#{name}\\t#{basename}' /etc/hosts } - # Restart network - service network restart + # Restart network (through NetworkManager if running) + if service NetworkManager status 2>&1 | grep -q running; then + service NetworkManager restart + else + service network restart + fi EOH end end diff --git a/plugins/guests/redhat/cap/configure_networks.rb b/plugins/guests/redhat/cap/configure_networks.rb index 6cbc9ec17..396457968 100644 --- a/plugins/guests/redhat/cap/configure_networks.rb +++ b/plugins/guests/redhat/cap/configure_networks.rb @@ -40,16 +40,23 @@ module VagrantPlugins # Down the interface before munging the config file. This might # fail if the interface is not actually set up yet so ignore # errors. - /sbin/ifdown '#{network[:device]}' || true - + /sbin/ifdown '#{network[:device]}' # Move new config into place - mv '#{remote_path}' '#{final_path}' - - # Bring the interface up - ARPCHECK=no /sbin/ifup '#{network[:device]}' + mv -f '#{remote_path}' '#{final_path}' + # attempt to force network manager to reload configurations + nmcli c reload || true EOH end + commands << <<-EOH.gsub(/^ {12}/, '') + # Restart network (through NetworkManager if running) + if service NetworkManager status 2>&1 | grep -q running; then + service NetworkManager restart + else + service network restart + fi + EOH + comm.sudo(commands.join("\n")) end end diff --git a/plugins/guests/redhat/cap/flavor.rb b/plugins/guests/redhat/cap/flavor.rb index 72e16f8da..32dc1a7c2 100644 --- a/plugins/guests/redhat/cap/flavor.rb +++ b/plugins/guests/redhat/cap/flavor.rb @@ -10,7 +10,7 @@ module VagrantPlugins end # Detect various flavors we care about - if output =~ /(CentOS|Red Hat Enterprise|Scientific|Cloud)\s*Linux( .+)? release 7/i + if output =~ /(CentOS|Red Hat Enterprise|Scientific|Cloud|Virtuozzo)\s*Linux( .+)? release 7/i return :rhel_7 else return :rhel diff --git a/plugins/guests/tinycore/guest.rb b/plugins/guests/tinycore/guest.rb index ae24f0066..d1fea1aed 100644 --- a/plugins/guests/tinycore/guest.rb +++ b/plugins/guests/tinycore/guest.rb @@ -1,11 +1,10 @@ -require "vagrant" +require_relative '../linux/guest' module VagrantPlugins module GuestTinyCore - class Guest < Vagrant.plugin("2", :guest) - def detect?(machine) - machine.communicate.test("cat /etc/issue | grep 'Core Linux'") - end + class Guest < VagrantPlugins::GuestLinux::Guest + # Name used for guest detection + GUEST_DETECTION_NAME = "Core Linux".freeze end end end diff --git a/plugins/guests/ubuntu/guest.rb b/plugins/guests/ubuntu/guest.rb index f60108eab..09d02b236 100644 --- a/plugins/guests/ubuntu/guest.rb +++ b/plugins/guests/ubuntu/guest.rb @@ -1,24 +1,10 @@ +require_relative '../linux/guest' + module VagrantPlugins module GuestUbuntu - class Guest < Vagrant.plugin("2", :guest) - def detect?(machine) - # This command detects if we are running on Ubuntu. /etc/os-release is - # available on modern Ubuntu versions, but does not exist on 14.04 and - # previous versions, so we fall back to lsb_release. - # - # GH-7524 - # GH-7625 - # - machine.communicate.test <<-EOH.gsub(/^ {10}/, "") - if test -r /etc/os-release; then - source /etc/os-release && test xubuntu = x$ID - elif test -x /usr/bin/lsb_release; then - /usr/bin/lsb_release -i 2>/dev/null | grep -q Ubuntu - else - exit 1 - fi - EOH - end + class Guest < VagrantPlugins::GuestLinux::Guest + # Name used for guest detection + GUEST_DETECTION_NAME = "ubuntu".freeze end end end diff --git a/plugins/hosts/linux/cap/nfs.rb b/plugins/hosts/linux/cap/nfs.rb index 90bf4d27b..731399b8e 100644 --- a/plugins/hosts/linux/cap/nfs.rb +++ b/plugins/hosts/linux/cap/nfs.rb @@ -6,6 +6,8 @@ module VagrantPlugins module HostLinux module Cap class NFS + + NFS_EXPORTS_PATH = "/etc/exports".freeze extend Vagrant::Util::Retryable def self.nfs_apply_command(env) @@ -36,16 +38,9 @@ module VagrantPlugins ui.info I18n.t("vagrant.hosts.linux.nfs_export") sleep 0.5 - nfs_cleanup(id) - - # Only use "sudo" if we can't write to /etc/exports directly - sudo_command = "" - sudo_command = "sudo " if !File.writable?("/etc/exports") - - output.split("\n").each do |line| - line = Vagrant::Util::ShellQuote.escape(line, "'") - system(%Q[echo '#{line}' | #{sudo_command}tee -a /etc/exports >/dev/null]) - end + nfs_cleanup("#{Process.uid} #{id}") + output = "#{nfs_exports_content}\n#{output}" + nfs_write_exports(output) if nfs_running?(nfs_check_command) system("sudo #{nfs_apply_command}") @@ -62,48 +57,111 @@ module VagrantPlugins end def self.nfs_prune(environment, ui, valid_ids) - return if !File.exist?("/etc/exports") + return if !File.exist?(NFS_EXPORTS_PATH) logger = Log4r::Logger.new("vagrant::hosts::linux") logger.info("Pruning invalid NFS entries...") - output = false user = Process.uid - File.read("/etc/exports").lines.each do |line| - if id = line[/^# VAGRANT-BEGIN:( #{user})? ([\.\/A-Za-z0-9\-_:]+?)$/, 2] - if valid_ids.include?(id) - logger.debug("Valid ID: #{id}") - else - if !output - # We want to warn the user but we only want to output once - ui.info I18n.t("vagrant.hosts.linux.nfs_prune") - output = true - end + # Create editor instance for removing invalid IDs + editor = Vagrant::Util::StringBlockEditor.new(nfs_exports_content) - logger.info("Invalid ID, pruning: #{id}") - nfs_cleanup(id) - end - end + # Build composite IDs with UID information and discover invalid entries + composite_ids = valid_ids.map do |v_id| + "#{user} #{v_id}" + end + remove_ids = editor.keys - composite_ids + + logger.debug("Known valid NFS export IDs: #{valid_ids}") + logger.debug("Composite valid NFS export IDs with user: #{composite_ids}") + logger.debug("NFS export IDs to be removed: #{remove_ids}") + if !remove_ids.empty? + ui.info I18n.t("vagrant.hosts.linux.nfs_prune") + nfs_cleanup(remove_ids) end end protected - def self.nfs_cleanup(id) - return if !File.exist?("/etc/exports") + def self.nfs_cleanup(remove_ids) + return if !File.exist?(NFS_EXPORTS_PATH) - user = Regexp.escape(Process.uid.to_s) - id = Regexp.escape(id.to_s) + editor = Vagrant::Util::StringBlockEditor.new(nfs_exports_content) + remove_ids = Array(remove_ids) - # Only use "sudo" if we can't write to /etc/exports directly - sudo_command = "" - sudo_command = "sudo " if !File.writable?("/etc/exports") + # Remove all invalid ID entries + remove_ids.each do |r_id| + editor.delete(r_id) + end + nfs_write_exports(editor.value) + end - # Use sed to just strip out the block of code which was inserted - # by Vagrant - tmp = ENV["TMPDIR"] || ENV["TMP"] || "/tmp" - system("cp /etc/exports '#{tmp}' && #{sudo_command}sed -r -e '\\\x01^# VAGRANT-BEGIN:( #{user})? #{id}\x01,\\\x01^# VAGRANT-END:( #{user})? #{id}\x01 d' -ibak '#{tmp}/exports' ; #{sudo_command}cp '#{tmp}/exports' /etc/exports") + def self.nfs_write_exports(new_exports_content) + if(nfs_exports_content != new_exports_content.strip) + begin + # Write contents out to temporary file + new_exports_file = Tempfile.create('vagrant') + new_exports_file.puts(new_exports_content) + new_exports_file.close + new_exports_path = new_exports_file.path + + # Only use "sudo" if we can't write to /etc/exports directly + sudo_command = "" + sudo_command = "sudo " if !File.writable?(NFS_EXPORTS_PATH) + + # Ensure new file mode and uid/gid match existing file to replace + existing_stat = File.stat(NFS_EXPORTS_PATH) + new_stat = File.stat(new_exports_path) + if existing_stat.mode != new_stat.mode + File.chmod(existing_stat.mode, new_exports_path) + end + if existing_stat.uid != new_stat.uid || existing_stat.gid != new_stat.gid + chown_cmd = "#{sudo_command}chown #{existing_stat.uid}:#{existing_stat.gid} #{new_exports_path}" + result = Vagrant::Util::Subprocess.execute(*Shellwords.split(chown_cmd)) + if result.exit_code != 0 + raise Vagrant::Errors::NFSExportsFailed, + command: chown_cmd, + stderr: result.stderr, + stdout: result.stdout + end + end + # Always force move the file to prevent overwrite prompting + mv_cmd = "#{sudo_command}mv -f #{new_exports_path} #{NFS_EXPORTS_PATH}" + result = Vagrant::Util::Subprocess.execute(*Shellwords.split(mv_cmd)) + if result.exit_code != 0 + raise Vagrant::Errors::NFSExportsFailed, + command: mv_cmd, + stderr: result.stderr, + stdout: result.stdout + end + ensure + if File.exist?(new_exports_path) + File.unlink(new_exports_path) + end + end + end + end + + def self.nfs_exports_content + if(File.exist?(NFS_EXPORTS_PATH)) + if(File.readable?(NFS_EXPORTS_PATH)) + File.read(NFS_EXPORTS_PATH) + else + cmd = "sudo cat #{NFS_EXPORTS_PATH}" + result = Vagrant::Util::Subprocess.execute(*Shellwords.split(cmd)) + if result.exit_code != 0 + raise Vagrant::Errors::NFSExportsFailed, + command: cmd, + stderr: result.stderr, + stdout: result.stdout + else + result.stdout + end + end + else + "" + end end def self.nfs_opts_setup(folders) diff --git a/plugins/kernel_v2/config/ssh.rb b/plugins/kernel_v2/config/ssh.rb index c9e0a6695..1dc3d51c9 100644 --- a/plugins/kernel_v2/config/ssh.rb +++ b/plugins/kernel_v2/config/ssh.rb @@ -15,24 +15,25 @@ module VagrantPlugins attr_accessor :ssh_command attr_accessor :pty attr_accessor :sudo_command + attr_accessor :export_command_template attr_reader :default def initialize super - @forward_agent = UNSET_VALUE - @forward_x11 = UNSET_VALUE - @forward_env = UNSET_VALUE - @guest_port = UNSET_VALUE - @keep_alive = UNSET_VALUE - @proxy_command = UNSET_VALUE - @ssh_command = UNSET_VALUE - @pty = UNSET_VALUE - @shell = UNSET_VALUE - @sudo_command = UNSET_VALUE - - @default = SSHConnectConfig.new + @forward_agent = UNSET_VALUE + @forward_x11 = UNSET_VALUE + @forward_env = UNSET_VALUE + @guest_port = UNSET_VALUE + @keep_alive = UNSET_VALUE + @proxy_command = UNSET_VALUE + @ssh_command = UNSET_VALUE + @pty = UNSET_VALUE + @shell = UNSET_VALUE + @sudo_command = UNSET_VALUE + @export_command_template = UNSET_VALUE + @default = SSHConnectConfig.new end def merge(other) @@ -55,6 +56,10 @@ module VagrantPlugins @pty = false if @pty == UNSET_VALUE @shell = "bash -l" if @shell == UNSET_VALUE + if @export_command_template == UNSET_VALUE + @export_command_template = 'export %ENV_KEY%="%ENV_VALUE%"' + end + if @sudo_command == UNSET_VALUE @sudo_command = "sudo -E -H %c" end diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 0cb7c8b1b..84a57f798 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -207,7 +207,19 @@ module VagrantPlugins hostpath = hostpath.to_s.gsub("\\", "/") end + if guestpath.is_a?(Hash) + options = guestpath + guestpath = nil + end + options ||= {} + + if options.has_key?(:name) + synced_folder_name = options.delete(:name) + else + synced_folder_name = guestpath + end + options[:guestpath] = guestpath.to_s.gsub(/\/$/, '') options[:hostpath] = hostpath options[:disabled] = false if !options.key?(:disabled) @@ -217,7 +229,7 @@ module VagrantPlugins # Make sure the type is a symbol options[:type] = options[:type].to_sym if options[:type] - @__synced_folders[options[:guestpath]] = options + @__synced_folders[synced_folder_name] = options end # Define a way to access the machine via a network. This exposes a @@ -581,7 +593,7 @@ module VagrantPlugins @hostname && @hostname !~ /^[a-z0-9][-.a-z0-9]*$/i if @box_version - @box_version.split(",").each do |v| + @box_version.to_s.split(",").each do |v| begin Gem::Requirement.new(v.strip) rescue Gem::Requirement::BadRequirementError @@ -626,7 +638,7 @@ module VagrantPlugins # If the shared folder is disabled then don't worry about validating it next if options[:disabled] - guestpath = Pathname.new(options[:guestpath]) + guestpath = Pathname.new(options[:guestpath]) if options[:guestpath] hostpath = Pathname.new(options[:hostpath]).expand_path(machine.env.root_path) if guestpath.to_s != "" diff --git a/plugins/providers/docker/driver.rb b/plugins/providers/docker/driver.rb index 421369a96..f7a06f195 100644 --- a/plugins/providers/docker/driver.rb +++ b/plugins/providers/docker/driver.rb @@ -48,7 +48,7 @@ module VagrantPlugins run_cmd += volumes.map { |v| ['-v', v.to_s] } run_cmd += %W(--privileged) if params[:privileged] run_cmd += %W(-h #{params[:hostname]}) if params[:hostname] - run_cmd << "-i" << "-t" if params[:pty] + run_cmd << "-t" if params[:pty] run_cmd << "--rm=true" if params[:rm] run_cmd += params[:extra_args] if params[:extra_args] run_cmd += [image, cmd] diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index 18525543e..548dbdc39 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -7,7 +7,7 @@ module VagrantPlugins module Action class Import def initialize(app, env) - @app = app + @app = app @logger = Log4r::Logger.new("vagrant::hyperv::import") end @@ -35,13 +35,28 @@ module VagrantPlugins end config_path = nil + config_type = nil vm_dir.each_child do |f| - if f.extname.downcase == ".xml" + if f.extname.downcase == '.xml' config_path = f + config_type = 'xml' break end end + # Only check for .vmcx if there is no XML found to not + # risk breaking older vagrant boxes that added an XML + # file manually + if config_type == nil + vm_dir.each_child do |f| + if f.extname.downcase == '.vmcx' + config_path = f + config_type = 'vmcx' + break + end + end + end + image_path = nil image_ext = nil image_filename = nil @@ -49,7 +64,7 @@ module VagrantPlugins if %w{.vhd .vhdx}.include?(f.extname.downcase) image_path = f image_ext = f.extname.downcase - image_filename = File.basename(f,image_ext) + image_filename = File.basename(f, image_ext) break end end @@ -100,19 +115,27 @@ module VagrantPlugins env[:ui].detail("Cloning virtual hard drive...") source_path = image_path.to_s - dest_path = env[:machine].data_dir.join("#{image_filename}#{image_ext}").to_s - if differencing_disk - env[:machine].provider.driver.execute("clone_vhd.ps1", {Source: source_path, Destination: dest_path}) - else - FileUtils.cp(source_path, dest_path) + dest_path = env[:machine].data_dir.join("Virtual Hard Disks").join("#{image_filename}#{image_ext}").to_s + + # Still hard copy the disk of old XML configurations + if config_type == 'xml' + if differencing_disk + env[:machine].provider.driver.execute("clone_vhd.ps1", {Source: source_path, Destination: dest_path}) + else + FileUtils.mkdir_p(env[:machine].data_dir.join("Virtual Hard Disks")) + FileUtils.cp(source_path, dest_path) + end end image_path = dest_path # We have to normalize the paths to be Windows paths since # we're executing PowerShell. options = { - vm_xml_config: config_path.to_s.gsub("/", "\\"), - image_path: image_path.to_s.gsub("/", "\\") + vm_config_file: config_path.to_s.gsub("/", "\\"), + vm_config_type: config_type, + source_path: source_path.to_s, + dest_path: dest_path, + data_path: env[:machine].data_dir.to_s.gsub("/", "\\") } options[:switchname] = switch if switch options[:memory] = memory if memory @@ -121,6 +144,7 @@ module VagrantPlugins options[:vmname] = vmname if vmname options[:auto_start_action] = auto_start_action if auto_start_action options[:auto_stop_action] = auto_stop_action if auto_stop_action + options[:differencing_disk] = differencing_disk if differencing_disk env[:ui].detail("Creating and registering the VM...") server = env[:machine].provider.driver.import(options) diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index 604c46bbd..1a2f1dd39 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -74,7 +74,15 @@ module VagrantPlugins end def import(options) - execute('import_vm.ps1', options) + config_type = options.delete(:vm_config_type) + if config_type === "vmcx" + execute('import_vm_vmcx.ps1', options) + else + options.delete(:data_path) + options.delete(:source_path) + options.delete(:differencing_disk) + execute('import_vm_xml.ps1', options) + end end def net_set_vlan(vlan_id) diff --git a/plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 b/plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 new file mode 100644 index 000000000..e4e13e913 --- /dev/null +++ b/plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 @@ -0,0 +1,157 @@ +Param( + [Parameter(Mandatory=$true)] + [string]$vm_config_file, + [Parameter(Mandatory=$true)] + [string]$source_path, + [Parameter(Mandatory=$true)] + [string]$dest_path, + [Parameter(Mandatory=$true)] + [string]$data_path, + + [string]$switchname=$null, + [string]$memory=$null, + [string]$maxmemory=$null, + [string]$cpus=$null, + [string]$vmname=$null, + [string]$auto_start_action=$null, + [string]$auto_stop_action=$null, + [string]$differencing_disk=$null +) + +# Include the following modules +$Dir = Split-Path $script:MyInvocation.MyCommand.Path +. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) + +$VmProperties = @{ + Path = $vm_config_file + SnapshotFilePath = Join-Path $data_path 'Snapshots' + VhdDestinationPath = Join-Path $data_path 'Virtual Hard Disks' + VirtualMachinePath = $data_path +} + +$vmConfig = (Compare-VM -Copy -GenerateNewID @VmProperties) + +$generation = $vmConfig.VM.Generation + +if (!$vmname) { + # Get the name of the vm + $vm_name = $vmconfig.VM.VMName +} else { + $vm_name = $vmname +} + +if (!$cpus) { + # Get the processorcount of the VM + $processors = (Get-VMProcessor -VM $vmConfig.VM).Count +}else { + $processors = $cpus +} + +function GetUniqueName($name) { + Get-VM | ForEach-Object -Process { + if ($name -eq $_.Name) { + $name = $name + "_1" + } + } + return $name +} + +do { + $name = $vm_name + $vm_name = GetUniqueName $name +} while ($vm_name -ne $name) + +if (!$memory) { + $configMemory = Get-VMMemory -VM $vmConfig.VM + $dynamicmemory = $configMemory.DynamicMemoryEnabled + + $MemoryMaximumBytes = ($configMemory.Maximum) + $MemoryStartupBytes = ($configMemory.Startup) + $MemoryMinimumBytes = ($configMemory.Minimum) +} else { + if (!$maxmemory){ + $dynamicmemory = $False + $MemoryMaximumBytes = ($memory -as [int]) * 1MB + $MemoryStartupBytes = ($memory -as [int]) * 1MB + $MemoryMinimumBytes = ($memory -as [int]) * 1MB + } else { + $dynamicmemory = $True + $MemoryMaximumBytes = ($maxmemory -as [int]) * 1MB + $MemoryStartupBytes = ($memory -as [int]) * 1MB + $MemoryMinimumBytes = ($memory -as [int]) * 1MB + } +} + +if (!$switchname) { + $switchname = (Get-VMNetworkAdapter -VM $vmConfig.VM).SwitchName +} + +$vmNetworkAdapter = Get-VMNetworkAdapter -VM $vmConfig.VM +Connect-VMNetworkAdapter -VMNetworkAdapter $vmNetworkAdapter -SwitchName $switchname +Set-VM -VM $vmConfig.VM -NewVMName $vm_name +Set-VM -VM $vmConfig.VM -ErrorAction "Stop" +Set-VM -VM $vmConfig.VM -ProcessorCount $processors + +if ($dynamicmemory) { + Set-VM -VM $vmConfig.VM -DynamicMemory + Set-VM -VM $vmConfig.VM -MemoryMinimumBytes $MemoryMinimumBytes -MemoryMaximumBytes $MemoryMaximumBytes -MemoryStartupBytes $MemoryStartupBytes +} else { + Set-VM -VM $vmConfig.VM -StaticMemory + Set-VM -VM $vmConfig.VM -MemoryStartupBytes $MemoryStartupBytes +} + +if ($notes) { + Set-VM -VM $vmConfig.VM -Notes $notes +} + +if ($auto_start_action) { + Set-VM -VM $vmConfig.VM -AutomaticStartAction $auto_start_action +} + +if ($auto_stop_action) { + Set-VM -VM $vmConfig.VM -AutomaticStartAction $auto_stop_action +} + +# Only set EFI secure boot for Gen 2 machines, not gen 1 +if ($generation -ne 1) { + Set-VMFirmware -VM $vmConfig.VM -EnableSecureBoot (Get-VMFirmware -VM $vmConfig.VM).SecureBoot +} + +$report = Compare-VM -CompatibilityReport $vmConfig + +# Stop if there are incompatibilities +if($report.Incompatibilities.Length -gt 0){ + Write-Error-Message $(ConvertTo-Json $($report.Incompatibilities | Select -ExpandProperty Message)) + exit 0 +} + +if($differencing_disk){ + # Get all controller on the VM, first scsi, then IDE if it is a Gen 1 device + $controllers = Get-VMScsiController -VM $vmConfig.VM + if($generation -eq 1){ + $controllers = @($controllers) + @(Get-VMIdeController -VM $vmConfig.VM) + } + + foreach($controller in $controllers){ + foreach($drive in $controller.Drives){ + if([System.IO.Path]::GetFileName($drive.Path) -eq [System.IO.Path]::GetFileName($source_path)){ + # Remove the old disk and replace it with a differencing version + $path = $drive.Path + Remove-VMHardDiskDrive $drive + New-VHD -Path $dest_path -ParentPath $source_path -ErrorAction Stop + Add-VMHardDiskDrive -VM $vmConfig.VM -Path $dest_path + } + } + } +} + +Import-VM -CompatibilityReport $vmConfig + +$vm_id = (Get-VM $vm_name).id.guid +$resultHash = @{ + name = $vm_name + id = $vm_id +} + +$result = ConvertTo-Json $resultHash +Write-Output-Message $result diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm_xml.ps1 similarity index 97% rename from plugins/providers/hyperv/scripts/import_vm.ps1 rename to plugins/providers/hyperv/scripts/import_vm_xml.ps1 index 659eb50aa..16055e135 100644 --- a/plugins/providers/hyperv/scripts/import_vm.ps1 +++ b/plugins/providers/hyperv/scripts/import_vm_xml.ps1 @@ -1,8 +1,8 @@ Param( [Parameter(Mandatory=$true)] - [string]$vm_xml_config, + [string]$vm_config_file, [Parameter(Mandatory=$true)] - [string]$image_path, + [string]$dest_path, [string]$switchname=$null, [string]$memory=$null, @@ -17,7 +17,7 @@ Param( $Dir = Split-Path $script:MyInvocation.MyCommand.Path . ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) -[xml]$vmconfig = Get-Content -Path $vm_xml_config +[xml]$vmconfig = Get-Content -Path $vm_config_file $generation = [int]($vmconfig.configuration.properties.subtype.'#text')+1 @@ -190,7 +190,7 @@ foreach ($controller in $controllers) { $addDriveParam = @{ ControllerNumber = $rx.Match($controller.node.name).value - Path = $image_path + Path = $dest_path } if ($drive.pool_id."#text") { diff --git a/plugins/provisioners/ansible/config/base.rb b/plugins/provisioners/ansible/config/base.rb index 67642467e..36fb5b903 100644 --- a/plugins/provisioners/ansible/config/base.rb +++ b/plugins/provisioners/ansible/config/base.rb @@ -4,16 +4,19 @@ module VagrantPlugins class Base < Vagrant.plugin("2", :config) GALAXY_COMMAND_DEFAULT = "ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force".freeze + PLAYBOOK_COMMAND_DEFAULT = "ansible-playbook".freeze + attr_accessor :config_file attr_accessor :extra_vars attr_accessor :galaxy_role_file attr_accessor :galaxy_roles_path attr_accessor :galaxy_command - attr_accessor :host_vars attr_accessor :groups + attr_accessor :host_vars attr_accessor :inventory_path attr_accessor :limit attr_accessor :playbook + attr_accessor :playbook_command attr_accessor :raw_arguments attr_accessor :skip_tags attr_accessor :start_at_task @@ -24,15 +27,17 @@ module VagrantPlugins attr_accessor :verbose def initialize + @config_file = UNSET_VALUE @extra_vars = UNSET_VALUE @galaxy_role_file = UNSET_VALUE @galaxy_roles_path = UNSET_VALUE @galaxy_command = UNSET_VALUE - @host_vars = UNSET_VALUE @groups = UNSET_VALUE + @host_vars = UNSET_VALUE @inventory_path = UNSET_VALUE @limit = UNSET_VALUE @playbook = UNSET_VALUE + @playbook_command = UNSET_VALUE @raw_arguments = UNSET_VALUE @skip_tags = UNSET_VALUE @start_at_task = UNSET_VALUE @@ -44,23 +49,25 @@ module VagrantPlugins end def finalize! - @extra_vars = nil if @extra_vars == UNSET_VALUE - @galaxy_role_file = nil if @galaxy_role_file == UNSET_VALUE - @galaxy_roles_path = nil if @galaxy_roles_path == UNSET_VALUE - @galaxy_command = GALAXY_COMMAND_DEFAULT if @galaxy_command == UNSET_VALUE - @host_vars = {} if @host_vars == UNSET_VALUE - @groups = {} if @groups == UNSET_VALUE - @inventory_path = nil if @inventory_path == UNSET_VALUE - @limit = nil if @limit == UNSET_VALUE - @playbook = nil if @playbook == UNSET_VALUE - @raw_arguments = nil if @raw_arguments == UNSET_VALUE - @skip_tags = nil if @skip_tags == UNSET_VALUE - @start_at_task = nil if @start_at_task == UNSET_VALUE - @sudo = false if @sudo != true - @sudo_user = nil if @sudo_user == UNSET_VALUE - @tags = nil if @tags == UNSET_VALUE - @vault_password_file = nil if @vault_password_file == UNSET_VALUE - @verbose = false if @verbose == UNSET_VALUE + @config_file = nil if @config_file == UNSET_VALUE + @extra_vars = nil if @extra_vars == UNSET_VALUE + @galaxy_role_file = nil if @galaxy_role_file == UNSET_VALUE + @galaxy_roles_path = nil if @galaxy_roles_path == UNSET_VALUE + @galaxy_command = GALAXY_COMMAND_DEFAULT if @galaxy_command == UNSET_VALUE + @groups = {} if @groups == UNSET_VALUE + @host_vars = {} if @host_vars == UNSET_VALUE + @inventory_path = nil if @inventory_path == UNSET_VALUE + @limit = nil if @limit == UNSET_VALUE + @playbook = nil if @playbook == UNSET_VALUE + @playbook_command = PLAYBOOK_COMMAND_DEFAULT if @playbook_command == UNSET_VALUE + @raw_arguments = nil if @raw_arguments == UNSET_VALUE + @skip_tags = nil if @skip_tags == UNSET_VALUE + @start_at_task = nil if @start_at_task == UNSET_VALUE + @sudo = false if @sudo != true + @sudo_user = nil if @sudo_user == UNSET_VALUE + @tags = nil if @tags == UNSET_VALUE + @vault_password_file = nil if @vault_password_file == UNSET_VALUE + @verbose = false if @verbose == UNSET_VALUE end # Just like the normal configuration "validate" method except that diff --git a/plugins/provisioners/ansible/provisioner/base.rb b/plugins/provisioners/ansible/provisioner/base.rb index bbfaa5290..8e52d751f 100644 --- a/plugins/provisioners/ansible/provisioner/base.rb +++ b/plugins/provisioners/ansible/provisioner/base.rb @@ -26,25 +26,44 @@ module VagrantPlugins end def check_files_existence - check_path_is_a_file config.playbook, :playbook + check_path_is_a_file(config.playbook, :playbook) - check_path_exists config.inventory_path, :inventory_path if config.inventory_path - check_path_is_a_file config.extra_vars[1..-1], :extra_vars if has_an_extra_vars_file_argument - check_path_is_a_file config.galaxy_role_file, :galaxy_role_file if config.galaxy_role_file - check_path_is_a_file config.vault_password_file, :vault_password if config.vault_password_file + check_path_exists(config.inventory_path, :inventory_path) if config.inventory_path + check_path_is_a_file(config.config_file, :config_file) if config.config_file + check_path_is_a_file(config.extra_vars[1..-1], :extra_vars) if has_an_extra_vars_file_argument + check_path_is_a_file(config.galaxy_role_file, :galaxy_role_file) if config.galaxy_role_file + check_path_is_a_file(config.vault_password_file, :vault_password_file) if config.vault_password_file + end + + def get_environment_variables_for_shell_execution + shell_env_vars = [] + @environment_variables.each_pair do |k, v| + if k =~ /ANSIBLE_SSH_ARGS|ANSIBLE_ROLES_PATH|ANSIBLE_CONFIG/ + shell_env_vars << "#{k}='#{v}'" + else + shell_env_vars << "#{k}=#{v}" + end + end + shell_env_vars + end + + def ansible_galaxy_command_for_shell_execution + command_values = { + role_file: "'#{get_galaxy_role_file}'", + roles_path: "'#{get_galaxy_roles_path}'" + } + + shell_command = get_environment_variables_for_shell_execution + + shell_command << config.galaxy_command % command_values + + shell_command.flatten.join(' ') end def ansible_playbook_command_for_shell_execution - shell_command = [] - @environment_variables.each_pair do |k, v| - if k =~ /ANSIBLE_SSH_ARGS|ANSIBLE_ROLES_PATH/ - shell_command << "#{k}='#{v}'" - else - shell_command << "#{k}=#{v}" - end - end + shell_command = get_environment_variables_for_shell_execution - shell_command << "ansible-playbook" + shell_command << config.playbook_command shell_args = [] @command_arguments.each do |arg| @@ -102,6 +121,12 @@ module VagrantPlugins # Use ANSIBLE_ROLES_PATH to tell ansible-playbook where to look for roles # (there is no equivalent command line argument in ansible-playbook) @environment_variables["ANSIBLE_ROLES_PATH"] = get_galaxy_roles_path if config.galaxy_roles_path + + prepare_ansible_config_environment_variable + end + + def prepare_ansible_config_environment_variable + @environment_variables["ANSIBLE_CONFIG"] = config.config_file if config.config_file end # Auto-generate "safe" inventory file based on Vagrantfile, diff --git a/plugins/provisioners/ansible/provisioner/guest.rb b/plugins/provisioners/ansible/provisioner/guest.rb index 3f8aa87c4..ef4287650 100644 --- a/plugins/provisioners/ansible/provisioner/guest.rb +++ b/plugins/provisioners/ansible/provisioner/guest.rb @@ -52,9 +52,9 @@ module VagrantPlugins @machine.guest.capability(:ansible_install, config.install_mode, config.version) end - # Check that ansible binaries are well installed on the guest, + # Check that Ansible Playbook command is available on the guest @machine.communicate.execute( - "ansible-galaxy info --help && ansible-playbook --help", + "test -x \"$(command -v #{config.playbook_command})\"", error_class: Ansible::Errors::AnsibleNotFoundOnGuest, error_key: :ansible_not_found_on_guest ) @@ -72,14 +72,9 @@ module VagrantPlugins end def execute_ansible_galaxy_on_guest - command_values = { - role_file: "'#{get_galaxy_role_file}'", - roles_path: "'#{get_galaxy_roles_path}'" - } + prepare_ansible_config_environment_variable - remote_command = config.galaxy_command % command_values - - execute_ansible_command_on_guest "galaxy", remote_command + execute_ansible_command_on_guest "galaxy", ansible_galaxy_command_for_shell_execution end def execute_ansible_playbook_on_guest @@ -156,7 +151,7 @@ module VagrantPlugins # and error if it doesn't exist. remote_path = Helpers::expand_path_in_unix_style(path, config.provisioning_path) - command = "test #{test_args} #{remote_path}" + command = "test #{test_args} '#{remote_path}'" @machine.communicate.execute( command, diff --git a/plugins/provisioners/ansible/provisioner/host.rb b/plugins/provisioners/ansible/provisioner/host.rb index 990b730dd..e0fc70f0b 100644 --- a/plugins/provisioners/ansible/provisioner/host.rb +++ b/plugins/provisioners/ansible/provisioner/host.rb @@ -20,6 +20,7 @@ module VagrantPlugins check_files_existence warn_for_unsupported_platform + execute_ansible_galaxy_from_host if config.galaxy_role_file execute_ansible_playbook_from_host end @@ -88,6 +89,8 @@ module VagrantPlugins end def execute_ansible_galaxy_from_host + prepare_ansible_config_environment_variable + command_values = { role_file: get_galaxy_role_file, roles_path: get_galaxy_roles_path @@ -97,23 +100,23 @@ module VagrantPlugins command = str_command.split(VAGRANT_ARG_SEPARATOR) command << { + env: @environment_variables, # Write stdout and stderr data, since it's the regular Ansible output notify: [:stdout, :stderr], workdir: @machine.env.root_path.to_s } - # FIXME: role_file and roles_path arguments should be quoted in the console output - ui_running_ansible_command "galaxy", str_command.gsub(VAGRANT_ARG_SEPARATOR, ' ') + ui_running_ansible_command "galaxy", ansible_galaxy_command_for_shell_execution execute_command_from_host command end def execute_ansible_playbook_from_host - prepare_command_arguments prepare_environment_variables + prepare_command_arguments # Assemble the full ansible-playbook command - command = %w(ansible-playbook) << @command_arguments + command = [config.playbook_command] << @command_arguments # Add the raw arguments at the end, to give them the highest precedence command << config.raw_arguments if config.raw_arguments @@ -234,6 +237,7 @@ module VagrantPlugins proxy_cmd += " exec nc %h %p 2>/dev/null" ssh_options << "-o ProxyCommand='#{ proxy_cmd }'" + # TODO ssh_options << "-o ProxyCommand=\"#{ proxy_cmd }\"" end # Use an SSH ProxyCommand when corresponding Vagrant setting is defined diff --git a/plugins/provisioners/chef/config/base.rb b/plugins/provisioners/chef/config/base.rb index ac5aa7732..9d033ecf4 100644 --- a/plugins/provisioners/chef/config/base.rb +++ b/plugins/provisioners/chef/config/base.rb @@ -64,16 +64,6 @@ module VagrantPlugins # @return [String] attr_accessor :installer_download_path - # @deprecated - def prerelease=(value) - STDOUT.puts <<-EOH -[DEPRECATED] The configuration `chef.prerelease' has been deprecated. Please use -`chef.channel' instead. The default value for channel is "stable", which -includes the latest published versions of the Chef Client. You can choose to use -prerelease versions by setting the channel to "current". -EOH - end - def initialize super diff --git a/plugins/provisioners/docker/config.rb b/plugins/provisioners/docker/config.rb index 8e9de55da..44409a41d 100644 --- a/plugins/provisioners/docker/config.rb +++ b/plugins/provisioners/docker/config.rb @@ -5,15 +5,6 @@ module VagrantPlugins class Config < Vagrant.plugin("2", :config) attr_reader :images - def version=(value) - STDOUT.puts <<-EOH -[DEPRECATED] The configuration `docker.version' has been deprecated. Docker no -longer allows you to specify the version of Docker you want installed and will -automatically choose the best version for your guest. Please remove this option -from your Vagrantfile. -EOH - end - def initialize @images = Set.new diff --git a/plugins/provisioners/puppet/config/puppet.rb b/plugins/provisioners/puppet/config/puppet.rb index 47215ca92..568de5fdb 100644 --- a/plugins/provisioners/puppet/config/puppet.rb +++ b/plugins/provisioners/puppet/config/puppet.rb @@ -13,6 +13,7 @@ module VagrantPlugins attr_accessor :manifests_path attr_accessor :environment attr_accessor :environment_path + attr_accessor :environment_variables attr_accessor :module_path attr_accessor :options attr_accessor :synced_folder_type @@ -23,18 +24,19 @@ module VagrantPlugins def initialize super - @binary_path = UNSET_VALUE - @hiera_config_path = UNSET_VALUE - @manifest_file = UNSET_VALUE - @manifests_path = UNSET_VALUE - @environment = UNSET_VALUE - @environment_path = UNSET_VALUE - @module_path = UNSET_VALUE - @options = [] - @facter = {} - @synced_folder_type = UNSET_VALUE - @temp_dir = UNSET_VALUE - @working_directory = UNSET_VALUE + @binary_path = UNSET_VALUE + @hiera_config_path = UNSET_VALUE + @manifest_file = UNSET_VALUE + @manifests_path = UNSET_VALUE + @environment = UNSET_VALUE + @environment_path = UNSET_VALUE + @environment_variables = UNSET_VALUE + @module_path = UNSET_VALUE + @options = [] + @facter = {} + @synced_folder_type = UNSET_VALUE + @temp_dir = UNSET_VALUE + @working_directory = UNSET_VALUE end def nfs=(value) @@ -87,6 +89,10 @@ module VagrantPlugins end end + if @environment_variables == UNSET_VALUE + @environment_variables = {} + end + @binary_path = nil if @binary_path == UNSET_VALUE @module_path = nil if @module_path == UNSET_VALUE @synced_folder_type = nil if @synced_folder_type == UNSET_VALUE diff --git a/plugins/provisioners/puppet/provisioner/puppet.rb b/plugins/provisioners/puppet/provisioner/puppet.rb index dcf6863aa..9f8912b7b 100644 --- a/plugins/provisioners/puppet/provisioner/puppet.rb +++ b/plugins/provisioners/puppet/provisioner/puppet.rb @@ -207,7 +207,7 @@ module VagrantPlugins options = options.join(" ") # Build up the custom facts if we have any - facter = "" + facter = nil if !config.facter.empty? facts = [] config.facter.each do |key, value| @@ -219,7 +219,7 @@ module VagrantPlugins facts.map! { |v| "$env:#{v};" } end - facter = "#{facts.join(" ")} " + facter = facts.join(" ") end puppet_bin = "puppet" @@ -227,7 +227,28 @@ module VagrantPlugins puppet_bin = File.join(@config.binary_path, puppet_bin) end - command = "#{facter} #{puppet_bin} apply #{options}" + env_vars = nil + if !config.environment_variables.nil? && !config.environment_variables.empty? + env_vars = config.environment_variables.map do |env_key, env_value| + "#{env_key}=\"#{env_value}\"" + end + + if windows? + env_vars.map! do |env_var_string| + "$env:#{env_var_string}" + end + end + + env_vars = env_vars.join(" ") + end + + command = [ + env_vars, + facter, + puppet_bin, + "apply", + options + ].compact.map(&:to_s).join(" ") if config.working_directory if windows? command = "cd #{config.working_directory}; if ($?) \{ #{command} \}" diff --git a/plugins/provisioners/salt/config.rb b/plugins/provisioners/salt/config.rb index c0e84d932..b42cfb00d 100644 --- a/plugins/provisioners/salt/config.rb +++ b/plugins/provisioners/salt/config.rb @@ -4,11 +4,6 @@ require "vagrant/util/deep_merge" module VagrantPlugins module Salt class Config < Vagrant.plugin("2", :config) - ## @deprecated - def config_dir=(value) - puts "salt config_dir is deprecated and will be removed in Vagrant 1.9" - end - ## salty-vagrant options attr_accessor :minion_config attr_accessor :minion_key @@ -74,12 +69,6 @@ module VagrantPlugins end def finalize! - @minion_config = nil if @minion_config == UNSET_VALUE - @minion_key = nil if @minion_key == UNSET_VALUE - @minion_pub = nil if @minion_pub == UNSET_VALUE - @master_config = nil if @master_config == UNSET_VALUE - @master_key = nil if @master_key == UNSET_VALUE - @master_pub = nil if @master_pub == UNSET_VALUE @grains_config = nil if @grains_config == UNSET_VALUE @run_highstate = nil if @run_highstate == UNSET_VALUE @run_overstate = nil if @run_overstate == UNSET_VALUE @@ -102,6 +91,15 @@ module VagrantPlugins @version = nil if @version == UNSET_VALUE @run_service = nil if @run_service == UNSET_VALUE @master_id = nil if @master_id == UNSET_VALUE + + # NOTE: Optimistic defaults are set in the provisioner. UNSET_VALUEs + # are converted there to allow proper detection of unset values. + # @minion_config = nil if @minion_config == UNSET_VALUE + # @minion_key = nil if @minion_key == UNSET_VALUE + # @minion_pub = nil if @minion_pub == UNSET_VALUE + # @master_config = nil if @master_config == UNSET_VALUE + # @master_key = nil if @master_key == UNSET_VALUE + # @master_pub = nil if @master_pub == UNSET_VALUE end def pillar(data) @@ -111,14 +109,14 @@ module VagrantPlugins def validate(machine) errors = _detected_errors - if @minion_config + if @minion_config && @minion_config != UNSET_VALUE expanded = Pathname.new(@minion_config).expand_path(machine.env.root_path) if !expanded.file? errors << I18n.t("vagrant.provisioners.salt.minion_config_nonexist", missing_config_file: expanded) end end - if @master_config + if @master_config && @master_config != UNSET_VALUE expanded = Pathname.new(@master_config).expand_path(machine.env.root_path) if !expanded.file? errors << I18n.t("vagrant.provisioners.salt.master_config_nonexist", missing_config_file: expanded) diff --git a/plugins/provisioners/salt/provisioner.rb b/plugins/provisioners/salt/provisioner.rb index eb9e16887..465edc563 100644 --- a/plugins/provisioners/salt/provisioner.rb +++ b/plugins/provisioners/salt/provisioner.rb @@ -3,7 +3,20 @@ require 'json' module VagrantPlugins module Salt class Provisioner < Vagrant.plugin("2", :provisioner) + + # Default path values to set within configuration only + # if configuration value is unset and local path exists + OPTIMISTIC_PATH_DEFAULTS = Hash[*[ + "minion_config", "salt/minion", + "minion_key", "salt/key/minion.key", + "minion_pub", "salt/key/minion.pub", + "master_config", "salt/master", + "master_key", "salt/key/master.key", + "master_pub", "salt/key/master.pub" + ].map(&:freeze)].freeze + def provision + set_default_configs upload_configs upload_keys run_bootstrap_script @@ -238,19 +251,19 @@ module VagrantPlugins bootstrap_path = get_bootstrap if @machine.config.vm.communicator == :winrm if @config.version - options += " -version %s" % @config.version + options += " -version %s" % @config.version end if @config.run_service @machine.env.ui.info "Salt minion will be stopped after installing." - options += " -runservice %s" % @config.run_service + options += " -runservice %s" % @config.run_service end if @config.minion_id @machine.env.ui.info "Setting minion to @config.minion_id." - options += " -minion %s" % @config.minion_id + options += " -minion %s" % @config.minion_id end if @config.master_id @machine.env.ui.info "Setting master to @config.master_id." - options += " -master %s" % @config.master_id + options += " -master %s" % @config.master_id end bootstrap_destination = File.join(config_dir, "bootstrap_salt.ps1") else @@ -390,6 +403,16 @@ module VagrantPlugins @machine.communicate.sudo(cmd, &log_output) end end + + # Sets optimistic default values into config + def set_default_configs + OPTIMISTIC_PATH_DEFAULTS.each do |config_key, config_default| + if config.send(config_key) == Config::UNSET_VALUE + config_value = File.exist?(expanded_path(config_default)) ? config_default : nil + config.send("#{config_key}=", config_value) + end + end + end end end end diff --git a/plugins/provisioners/shell/config.rb b/plugins/provisioners/shell/config.rb index d5f260b91..049eb15d0 100644 --- a/plugins/provisioners/shell/config.rb +++ b/plugins/provisioners/shell/config.rb @@ -5,6 +5,8 @@ module VagrantPlugins class Config < Vagrant.plugin("2", :config) attr_accessor :inline attr_accessor :path + attr_accessor :md5 + attr_accessor :sha1 attr_accessor :env attr_accessor :upload_path attr_accessor :args @@ -19,6 +21,8 @@ module VagrantPlugins @args = UNSET_VALUE @inline = UNSET_VALUE @path = UNSET_VALUE + @md5 = UNSET_VALUE + @sha1 = UNSET_VALUE @env = UNSET_VALUE @upload_path = UNSET_VALUE @privileged = UNSET_VALUE @@ -33,6 +37,8 @@ module VagrantPlugins @args = nil if @args == UNSET_VALUE @inline = nil if @inline == UNSET_VALUE @path = nil if @path == UNSET_VALUE + @md5 = nil if @md5 == UNSET_VALUE + @sha1 = nil if @sha1 == UNSET_VALUE @env = {} if @env == UNSET_VALUE @upload_path = "/tmp/vagrant-shell" if @upload_path == UNSET_VALUE @privileged = true if @privileged == UNSET_VALUE diff --git a/plugins/provisioners/shell/provisioner.rb b/plugins/provisioners/shell/provisioner.rb index 13d4d112e..7c06d19df 100644 --- a/plugins/provisioners/shell/provisioner.rb +++ b/plugins/provisioners/shell/provisioner.rb @@ -177,7 +177,12 @@ module VagrantPlugins download_path.delete if download_path.file? begin - Vagrant::Util::Downloader.new(config.path, download_path).download! + Vagrant::Util::Downloader.new( + config.path, + download_path, + md5: config.md5, + sha1: config.sha1 + ).download! ext = File.extname(config.path) script = download_path.read ensure diff --git a/plugins/synced_folders/rsync/default_unix_cap.rb b/plugins/synced_folders/rsync/default_unix_cap.rb index 6e073fde5..dcf4b5a34 100644 --- a/plugins/synced_folders/rsync/default_unix_cap.rb +++ b/plugins/synced_folders/rsync/default_unix_cap.rb @@ -32,7 +32,7 @@ module VagrantPlugins exclude_base = Pathname.new(opts[:guestpath]) exclusions = Array(opts[:exclude]).map do |ex_path| ex_path = ex_path.slice(1, ex_path.size) if ex_path.start_with?(File::SEPARATOR) - "-path #{exclude_base.join(ex_path)} -prune" + "-path #{Shellwords.escape(exclude_base.join(ex_path))} -prune" end.join(" -o ") + " -o " end "find #{guest_path} #{exclusions}" \ diff --git a/templates/locales/en.yml b/templates/locales/en.yml old mode 100755 new mode 100644 index 75db2d500..1dc166863 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -734,6 +734,14 @@ en: downloader_interrupted: |- The download was interrupted by an external signal. It did not complete. + downloader_checksum_error: |- + The calculated checksum of the requested file does not match the expected + checksum! + + File source: %{source} + Checsum type: %{type} + Expected checksum: %{expected_checksum} + Calculated checksum: %{actual_checksum} env_inval: |- Vagrant received an "EINVAL" error while attempting to set some environment variables. This is usually caused by the total size of your @@ -877,6 +885,14 @@ en: the issues below and execute "vagrant reload": %{output} + nfs_exports_failed: |- + Vagrant failed to install an updated NFS exports file. This may be + due to overly restrictive permissions on your NFS exports file. Please + validate them and try again. + + command: %{command} + stdout: %{stdout} + stderr: %{stderr} nfs_cant_read_exports: |- Vagrant can't read your current NFS exports! The exports file should be readable by any user. This is usually caused by invalid permissions @@ -966,6 +982,20 @@ en: by contacting a plugin author to see if they can address the conflict. %{conflicts} + plugin_init_error: |- + The plugins failed to initialize correctly. This may be due to manual + modifications made within the Vagrant home directory. Vagrant can + attempt to automatically correct this issue by running: + + vagrant plugin repair + + If Vagrant was recently updated, this error may be due to incompatible + versions of dependencies. To fix this problem please remove and re-install + all plugins. Vagrant can attempt to do this automatically by running: + + vagrant plugin expunge --reinstall + + Error message given during initialization: %{message} plugin_load_error: |- The plugins failed to load properly. The error message given is shown below. @@ -1065,6 +1095,15 @@ en: Guest path: %{guestpath} Command: %{command} Error: %{stderr} + rsync_guest_install_error: |- + Installation of rsync into the guest has failed! The stdout + and stderr are shown below. Please read the error output, resolve + it and try again. If the problem persists, please install rsync + manually within the guest. + + Command: %{command} + Stdout: %{stdout} + Stderr: %{stderr} rsync_not_found: |- "rsync" could not be found on your PATH. Make sure that rsync is properly installed on your system and available on the PATH. @@ -1559,6 +1598,24 @@ en: the comments in the Vagrantfile as well as documentation on `vagrantup.com` for more information on using Vagrant. plugin: + expunge_confirm: |- + + This command permanently deletes all currently installed user plugins. It + should only be used when a repair command is unable to properly fix the + system. + + Continue? + expunge_request_reinstall: |- + Would you like Vagrant to attempt to reinstall current plugins? + expunge_complete: |- + + All user installed plugins have been removed from this Vagrant environment! + expunge_reinstall: |- + + Vagrant will now attempt to reinstall user plugins that were removed. + expunge_aborted: |- + + Vagrant expunge has been declined. Skipping removal of plugins. installed_license: |- The license for '%{name}' was successfully installed! installing_license: |- @@ -1584,6 +1641,20 @@ en: post_install: |- Post install message from the '%{name}' plugin: + %{message} + repairing: |- + Repairing currently installed plugins. This may take a few minutes... + repair_complete: |- + Installed plugins successfully repaired! + repair_failed: |- + Failed to automatically repair installed Vagrant plugins. To fix this + problem remove all user installed plugins and reinstall. Vagrant can + do this for you automatically by running the following command: + + vagrant plugin expunge --reinstall + + Failure message received during repair: + %{message} snapshot: not_supported: |- diff --git a/test/unit/plugins/commands/box/command/prune_test.rb b/test/unit/plugins/commands/box/command/prune_test.rb new file mode 100644 index 000000000..e85be01e7 --- /dev/null +++ b/test/unit/plugins/commands/box/command/prune_test.rb @@ -0,0 +1,194 @@ +require File.expand_path("../../../../../base", __FILE__) + +require Vagrant.source_root.join("plugins/commands/box/command/prune") + +describe VagrantPlugins::CommandBox::Command::Prune do + include_context "unit" + include_context "command plugin helpers" + + let(:entry_klass) { Vagrant::MachineIndex::Entry } + + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + isolated_environment.tap do |env| + env.vagrantfile("") + end + end + + let(:iso_vagrant_env) { iso_env.create_vagrant_env } + + let(:argv) { [] } + + # Seems this way of providing a box version triggers box in use. + def new_entry(name, box_name, box_provider, version) + entry_klass.new.tap do |e| + e.name = name + e.vagrantfile_path = "/bar" + e.extra_data["box"] = { + "name" => box_name, + "provider" => box_provider, + "version" => version, + } + end + end + + subject { described_class.new(argv, iso_vagrant_env) } + + describe "execute" do + context "with no args" do + it "removes the old version and keeps the current one" do + + # Let's put some things in the index + iso_env.box3("foobox", "1.0", :virtualbox); + iso_env.box3("foobox", "1.1", :virtualbox); + iso_env.box3("barbox", "1.0", :vmware); + iso_env.box3("barbox", "1.1", :vmware); + + iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1)) + + output = "" + allow(iso_vagrant_env.ui).to receive(:info) do |data| + output << data + end + expect(iso_vagrant_env.boxes.all.count).to eq(4) + expect(subject.execute).to eq(0) + expect(iso_vagrant_env.boxes.all.count).to eq(2) + + expect(output).to include("barbox (vmware, 1.1)") + expect(output).to include("Removing box 'barbox' (v1.0) with provider 'vmware'...") + expect(output).to include("foobox (virtualbox, 1.1)") + expect(output).to include("Removing box 'foobox' (v1.0) with provider 'virtualbox'...") + end + + it "removes nothing" do + # Let's put some things in the index + iso_env.box3("foobox", "1.0", :virtualbox); + iso_env.box3("barbox", "1.0", :vmware); + + iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1)) + + output = "" + allow(iso_vagrant_env.ui).to receive(:info) do |data| + output << data + end + expect(iso_vagrant_env.boxes.all.count).to eq(2) + expect(subject.execute).to eq(0) + expect(iso_vagrant_env.boxes.all.count).to eq(2) + + expect(output).to include("No old versions of boxes to remove...") + + end + end + + context "with --provider" do + let(:argv) { ["--provider", "virtualbox"] } + + it "removes the old versions of the specified provider" do + + # Let's put some things in the index + iso_env.box3("foobox", "1.0", :virtualbox); + iso_env.box3("foobox", "1.1", :virtualbox); + iso_env.box3("barbox", "1.0", :vmware); + iso_env.box3("barbox", "1.1", :vmware); + + iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1)) + + output = "" + allow(iso_vagrant_env.ui).to receive(:info) do |data| + output << "\n" + data + end + + expect(iso_vagrant_env.boxes.all.count).to eq(4) + expect(subject.execute).to eq(0) + expect(iso_vagrant_env.boxes.all.count).to eq(3) + + expect(output).to include("foobox (virtualbox, 1.1)") + expect(output).to include("Removing box 'foobox' (v1.0) with provider 'virtualbox'...") + + end + end + + context "with --dry-run" do + let(:argv) { ["--dry-run"] } + + it "removes the old versions of the specified provider" do + + # Let's put some things in the index + iso_env.box3("foobox", "1.0", :virtualbox); + iso_env.box3("foobox", "1.1", :virtualbox); + + iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1)) + + output = "" + allow(iso_vagrant_env.ui).to receive(:info) do |data| + output << "\n" + data + end + + expect(iso_vagrant_env.boxes.all.count).to eq(2) + expect(subject.execute).to eq(0) + expect(iso_vagrant_env.boxes.all.count).to eq(2) + + + expect(output).to include("foobox (virtualbox, 1.1)") + expect(output).to include("Would remove foobox virtualbox 1.0") + + + end + end + + context "with --name" do + let(:argv) { ["--name", "barbox"] } + + it "removes the old versions of the specified provider" do + + # Let's put some things in the index + iso_env.box3("foobox", "1.0", :virtualbox); + iso_env.box3("foobox", "1.1", :virtualbox); + iso_env.box3("barbox", "1.0", :vmware); + iso_env.box3("barbox", "1.1", :vmware); + + iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1)) + + output = "" + allow(iso_vagrant_env.ui).to receive(:info) do |data| + output << "\n" + data + end + + expect(iso_vagrant_env.boxes.all.count).to eq(4) + expect(subject.execute).to eq(0) + expect(iso_vagrant_env.boxes.all.count).to eq(3) + + expect(output).to include("barbox (vmware, 1.1)") + expect(output).to include("Removing box 'barbox' (v1.0) with provider 'vmware'...") + end + end + + + context "with --name and --provider" do + let(:argv) { ["--name", "foobox", "--provider", "virtualbox"] } + + it "removed the old versions of that name and provider only" do + # Let's put some things in the index + iso_env.box3("foobox", "1.0", :virtualbox); + iso_env.box3("foobox", "1.1", :virtualbox); + iso_env.box3("foobox", "1.0", :vmware); + iso_env.box3("foobox", "1.1", :vmware); + iso_env.box3("barbox", "1.0", :vmware); + iso_env.box3("barbox", "1.1", :vmware); + + iso_vagrant_env.machine_index.set(new_entry("foo", "foobox", "virtualbox", 1)) + + output = "" + allow(iso_vagrant_env.ui).to receive(:info) do |data| + output << "\n" + data + end + + expect(iso_vagrant_env.boxes.all.count).to eq(6) + expect(subject.execute).to eq(0) + expect(iso_vagrant_env.boxes.all.count).to eq(5) + + expect(output).to include("Removing box 'foobox' (v1.0) with provider 'virtualbox'...") + end + end + end +end diff --git a/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb new file mode 100644 index 000000000..de0d976de --- /dev/null +++ b/test/unit/plugins/commands/plugin/action/expunge_plugins_test.rb @@ -0,0 +1,64 @@ +require File.expand_path("../../../../../base", __FILE__) + +describe VagrantPlugins::CommandPlugin::Action::ExpungePlugins do + let(:app) { lambda { |env| } } + let(:home_path){ '/fake/file/path/.vagrant.d' } + let(:gems_path){ "#{home_path}/gems" } + let(:force){ true } + let(:env) {{ + ui: Vagrant::UI::Silent.new, + home_path: home_path, + gems_path: gems_path, + force: force + }} + + let(:manager) { double("manager") } + + let(:expect_to_receive) do + lambda do + allow(File).to receive(:exist?).with(File.join(home_path, 'plugins.json')).and_return(true) + allow(File).to receive(:directory?).with(gems_path).and_return(true) + expect(FileUtils).to receive(:rm).with(File.join(home_path, 'plugins.json')) + expect(FileUtils).to receive(:rm_rf).with(gems_path) + expect(app).to receive(:call).with(env).once + end + end + + subject { described_class.new(app, env) } + + before do + Vagrant::Plugin::Manager.stub(instance: manager) + end + + describe "#call" do + before do + instance_exec(&expect_to_receive) + end + + it "should delete all plugins" do + subject.call(env) + end + + describe "when force is false" do + let(:force){ false } + + it "should prompt user before deleting all plugins" do + expect(env[:ui]).to receive(:ask).and_return("Y\n") + subject.call(env) + end + + describe "when user declines prompt" do + let(:expect_to_receive) do + lambda do + expect(app).not_to receive(:call) + end + end + + it "should not delete all plugins" do + expect(env[:ui]).to receive(:ask).and_return("N\n") + subject.call(env) + end + end + end + end +end diff --git a/test/unit/plugins/commands/plugin/action/update_gems_test.rb b/test/unit/plugins/commands/plugin/action/update_gems_test.rb index 50e56267b..a3b4661c2 100644 --- a/test/unit/plugins/commands/plugin/action/update_gems_test.rb +++ b/test/unit/plugins/commands/plugin/action/update_gems_test.rb @@ -18,12 +18,14 @@ describe VagrantPlugins::CommandPlugin::Action::UpdateGems do describe "#call" do it "should update all plugins if none are specified" do expect(manager).to receive(:update_plugins).with([]).once.and_return([]) + expect(manager).to receive(:installed_plugins).twice.and_return({}) expect(app).to receive(:call).with(env).once subject.call(env) end it "should update specified plugins" do expect(manager).to receive(:update_plugins).with(["foo"]).once.and_return([]) + expect(manager).to receive(:installed_plugins).twice.and_return({}) expect(app).to receive(:call).with(env).once env[:plugin_name] = ["foo"] diff --git a/test/unit/plugins/communicators/ssh/communicator_test.rb b/test/unit/plugins/communicators/ssh/communicator_test.rb index d38694196..7de5b90d6 100644 --- a/test/unit/plugins/communicators/ssh/communicator_test.rb +++ b/test/unit/plugins/communicators/ssh/communicator_test.rb @@ -5,6 +5,8 @@ require Vagrant.source_root.join("plugins/communicators/ssh/communicator") describe VagrantPlugins::CommunicatorSSH::Communicator do include_context "unit" + let(:export_command_template){ 'export %ENV_KEY%="%ENV_VALUE%"' } + # SSH configuration information mock let(:ssh) do double("ssh", @@ -15,6 +17,7 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do pty: false, keep_alive: false, insert_key: false, + export_command_template: export_command_template, shell: 'bash -l' ) end @@ -509,4 +512,18 @@ describe VagrantPlugins::CommunicatorSSH::Communicator do end end end + + describe ".generate_environment_export" do + it "should generate bourne shell compatible export" do + communicator.send(:generate_environment_export, "TEST", "value").should eq("export TEST=\"value\"\n") + end + + context "with custom template defined" do + let(:export_command_template){ "setenv %ENV_KEY% %ENV_VALUE%" } + + it "should generate custom export based on template" do + communicator.send(:generate_environment_export, "TEST", "value").should eq("setenv TEST value\n") + end + end + end end diff --git a/test/unit/plugins/communicators/winrm/communicator_test.rb b/test/unit/plugins/communicators/winrm/communicator_test.rb index 3e21242d9..d69185d86 100644 --- a/test/unit/plugins/communicators/winrm/communicator_test.rb +++ b/test/unit/plugins/communicators/winrm/communicator_test.rb @@ -45,7 +45,7 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do port: '22', }) # Makes ready? return true - allow(shell).to receive(:powershell).with("hostname").and_return({ exitcode: 0 }) + allow(shell).to receive(:cmd).with("hostname").and_return({ exitcode: 0 }) end it "retries ssh_info until ready" do @@ -57,22 +57,22 @@ describe VagrantPlugins::CommunicatorWinRM::Communicator do describe ".ready?" do it "returns true if hostname command executes without error" do - expect(shell).to receive(:powershell).with("hostname").and_return({ exitcode: 0 }) + expect(shell).to receive(:cmd).with("hostname").and_return({ exitcode: 0 }) expect(subject.ready?).to be_true end it "returns false if hostname command fails with a transient error" do - expect(shell).to receive(:powershell).with("hostname").and_raise(VagrantPlugins::CommunicatorWinRM::Errors::TransientError) + expect(shell).to receive(:cmd).with("hostname").and_raise(VagrantPlugins::CommunicatorWinRM::Errors::TransientError) expect(subject.ready?).to be_false end it "raises an error if hostname command fails with an unknown error" do - expect(shell).to receive(:powershell).with("hostname").and_raise(Vagrant::Errors::VagrantError) + expect(shell).to receive(:cmd).with("hostname").and_raise(Vagrant::Errors::VagrantError) expect { subject.ready? }.to raise_error(Vagrant::Errors::VagrantError) end it "raises timeout error when hostname command takes longer then winrm timeout" do - expect(shell).to receive(:powershell).with("hostname") do + expect(shell).to receive(:cmd).with("hostname") do sleep 2 # winrm.timeout = 1 end expect { subject.ready? }.to raise_error(Timeout::Error) diff --git a/test/unit/plugins/communicators/winrm/shell_test.rb b/test/unit/plugins/communicators/winrm/shell_test.rb index 5cf1f6810..0c77290c8 100644 --- a/test/unit/plugins/communicators/winrm/shell_test.rb +++ b/test/unit/plugins/communicators/winrm/shell_test.rb @@ -6,7 +6,7 @@ require Vagrant.source_root.join("plugins/communicators/winrm/config") describe VagrantPlugins::CommunicatorWinRM::WinRMShell do include_context "unit" - let(:session) { double("winrm_session", create_executor: executor) } + let(:session) { double("winrm_session") } let(:executor) { double("command_executor") } let(:port) { config.transport == :ssl ? 5986 : 5985 } let(:config) { @@ -22,6 +22,8 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do end } + before { allow(session).to receive(:create_executor).and_yield(executor) } + subject do described_class.new('localhost', port, config).tap do |comm| allow(comm).to receive(:new_session).and_return(session) diff --git a/test/unit/plugins/guests/bsd/cap/nfs_test.rb b/test/unit/plugins/guests/bsd/cap/nfs_test.rb index 66b1adde2..aec465b9d 100644 --- a/test/unit/plugins/guests/bsd/cap/nfs_test.rb +++ b/test/unit/plugins/guests/bsd/cap/nfs_test.rb @@ -31,10 +31,9 @@ describe "VagrantPlugins::GuestBSD::Cap::NFS" do } cap.mount_nfs_folder(machine, ip, folders) - expect(comm.received_commands[0]).to match(/set -e/) expect(comm.received_commands[0]).to match(/mkdir -p \/guest/) - expect(comm.received_commands[0]).to match(/mount -t nfs/) - expect(comm.received_commands[0]).to match(/1.2.3.4:\/host \/guest/) + expect(comm.received_commands[1]).to match(/mount -t nfs/) + expect(comm.received_commands[1]).to match(/1.2.3.4:\/host \/guest/) end it "mounts with options" do @@ -49,7 +48,7 @@ describe "VagrantPlugins::GuestBSD::Cap::NFS" do } cap.mount_nfs_folder(machine, ip, folders) - expect(comm.received_commands[0]).to match(/mount -t nfs -o 'nfsv2,mntudp,banana'/) + expect(comm.received_commands[1]).to match(/mount -t nfs -o 'nfsv2,mntudp,banana'/) end it "escapes host and guest paths" do @@ -61,8 +60,8 @@ describe "VagrantPlugins::GuestBSD::Cap::NFS" do } cap.mount_nfs_folder(machine, ip, folders) - expect(comm.received_commands[0]).to match(/host\\\'s/) - expect(comm.received_commands[0]).to match(/guest\\\ with\\\ spaces/) + expect(comm.received_commands[1]).to match(/host\\\'s/) + expect(comm.received_commands[1]).to match(/guest\\\ with\\\ spaces/) end end end diff --git a/test/unit/plugins/guests/linux/cap/mount_nfs_test.rb b/test/unit/plugins/guests/linux/cap/mount_nfs_test.rb index 5fcd55de4..7cec549f2 100644 --- a/test/unit/plugins/guests/linux/cap/mount_nfs_test.rb +++ b/test/unit/plugins/guests/linux/cap/mount_nfs_test.rb @@ -43,7 +43,7 @@ describe "VagrantPlugins::GuestLinux::Cap::MountNFS" do cap.mount_nfs_folder(machine, ip, folders) expect(comm.received_commands[0]).to match(/mkdir -p #{guestpath}/) - expect(comm.received_commands[0]).to match(/1.2.3.4:#{hostpath} #{guestpath}/) + expect(comm.received_commands[1]).to match(/1.2.3.4:#{hostpath} #{guestpath}/) end it "mounts with options" do @@ -58,7 +58,7 @@ describe "VagrantPlugins::GuestLinux::Cap::MountNFS" do } cap.mount_nfs_folder(machine, ip, folders) - expect(comm.received_commands[0]).to match(/mount -o vers=2,udp/) + expect(comm.received_commands[1]).to match(/mount -o vers=2,udp/) end it "emits an event" do @@ -71,7 +71,7 @@ describe "VagrantPlugins::GuestLinux::Cap::MountNFS" do } cap.mount_nfs_folder(machine, ip, folders) - expect(comm.received_commands[0]).to include( + expect(comm.received_commands[1]).to include( "/sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guestpath}") end @@ -84,8 +84,8 @@ describe "VagrantPlugins::GuestLinux::Cap::MountNFS" do } cap.mount_nfs_folder(machine, ip, folders) - expect(comm.received_commands[0]).to match(/host\\\'s/) - expect(comm.received_commands[0]).to match(/guest\\\ with\\\ spaces/) + expect(comm.received_commands[1]).to match(/host\\\'s/) + expect(comm.received_commands[1]).to match(/guest\\\ with\\\ spaces/) end end end diff --git a/test/unit/plugins/guests/linux/cap/network_interfaces_test.rb b/test/unit/plugins/guests/linux/cap/network_interfaces_test.rb index 8dd4498a7..a7d34fe5f 100644 --- a/test/unit/plugins/guests/linux/cap/network_interfaces_test.rb +++ b/test/unit/plugins/guests/linux/cap/network_interfaces_test.rb @@ -44,5 +44,41 @@ describe "VagrantPlugins::GuestLinux::Cap::NetworkInterfaces" do result = cap.network_interfaces(machine) expect(result).to eq(["enp0s3", "enp0s5", "enp0s8", "enp0s10", "enp1s3"]) end + + it "sorts ethernet devices discovered with classic naming first in list" do + expect(comm).to receive(:sudo).and_yield(:stdout, "eth1\neth2\ndocker0\nbridge0\neth0") + result = cap.network_interfaces(machine) + expect(result).to eq(["eth0", "eth1", "eth2", "bridge0", "docker0"]) + end + + it "sorts ethernet devices discovered with predictable network interfaces naming first in list" do + expect(comm).to receive(:sudo).and_yield(:stdout, "enp0s8\ndocker0\nenp0s3\nbridge0\nenp0s5") + result = cap.network_interfaces(machine) + expect(result).to eq(["enp0s3", "enp0s5", "enp0s8", "bridge0", "docker0"]) + end + + it "sorts ethernet devices discovered with predictable network interfaces naming first in list with less" do + expect(comm).to receive(:sudo).and_yield(:stdout, "enp0s3\nenp0s8\ndocker0") + result = cap.network_interfaces(machine) + expect(result).to eq(["enp0s3", "enp0s8", "docker0"]) + end + + it "does not include ethernet devices aliases within prefix device listing" do + expect(comm).to receive(:sudo).and_yield(:stdout, "eth1\neth2\ndocker0\nbridge0\neth0\ndocker1\neth0:0") + result = cap.network_interfaces(machine) + expect(result).to eq(["eth0", "eth1", "eth2", "bridge0", "docker0", "docker1", "eth0:0"]) + end + + it "does not include ethernet devices aliases within prefix device listing with dot separators" do + expect(comm).to receive(:sudo).and_yield(:stdout, "eth1\neth2\ndocker0\nbridge0\neth0\ndocker1\neth0.1@eth0") + result = cap.network_interfaces(machine) + expect(result).to eq(["eth0", "eth1", "eth2", "bridge0", "docker0", "docker1", "eth0.1@eth0"]) + end + + it "properly sorts non-consistent device name formats" do + expect(comm).to receive(:sudo).and_yield(:stdout, "eth0\neth1\ndocker0\nveth437f7f9\nveth06b3e44\nveth8bb7081") + result = cap.network_interfaces(machine) + expect(result).to eq(["eth0", "eth1", "docker0", "veth8bb7081", "veth437f7f9", "veth06b3e44"]) + end end end diff --git a/test/unit/plugins/guests/linux/cap/rsync_test.rb b/test/unit/plugins/guests/linux/cap/rsync_test.rb index 4676cdf04..01f4e6156 100644 --- a/test/unit/plugins/guests/linux/cap/rsync_test.rb +++ b/test/unit/plugins/guests/linux/cap/rsync_test.rb @@ -70,15 +70,27 @@ describe "VagrantPlugins::GuestLinux::Cap::Rsync" do end context "with excludes provided" do - let(:excludes){ ["tmp", "state/*"] } + let(:excludes){ ["tmp", "state/*", "path/with a/space"] } it "ignores files that are excluded" do - comm.expect_command( - "find #{guest_directory} -path #{File.join(guest_directory, excludes.first)} -prune -o " \ - "-path #{File.join(guest_directory, excludes.last)} -prune -o '!' -type l -a '(' ! -user " \ - "#{owner} -or ! -group #{group} ')' -exec chown #{owner}:#{group} '{}' +" - ) + # comm.expect_command( + # "find #{guest_directory} -path #{Shellwords.escape(File.join(guest_directory, excludes.first))} -prune -o " \ + # "-path #{Shellwords.escape(File.join(guest_directory, excludes.last))} -prune -o '!' " \ + # "-path -type l -a '(' ! -user " \ + # "#{owner} -or ! -group #{group} ')' -exec chown #{owner}:#{group} '{}' +" + # ) cap.rsync_post(machine, options) + excludes.each do |ex_path| + expect(comm.received_commands.first).to include("-path #{Shellwords.escape(File.join(guest_directory, ex_path))} -prune") + end + end + + it "properly escapes excluded directories" do + cap.rsync_post(machine, options) + exclude_with_space = excludes.detect{|ex| ex.include?(' ')} + escaped_exclude_with_space = Shellwords.escape(exclude_with_space) + expect(comm.received_commands.first).not_to include(exclude_with_space) + expect(comm.received_commands.first).to include(escaped_exclude_with_space) end end end diff --git a/test/unit/plugins/guests/openbsd/cap/rsync_test.rb b/test/unit/plugins/guests/openbsd/cap/rsync_test.rb new file mode 100644 index 000000000..68874b18f --- /dev/null +++ b/test/unit/plugins/guests/openbsd/cap/rsync_test.rb @@ -0,0 +1,59 @@ +require_relative "../../../../base" + +describe "VagrantPlugins::GuestOpenBSD::Cap::RSync" do + let(:caps) do + VagrantPlugins::GuestOpenBSD::Plugin + .components + .guest_capabilities[:openbsd] + end + + let(:machine) { double("machine") } + let(:comm) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + allow(machine).to receive(:communicate).and_return(comm) + end + + after do + comm.verify_expectations! + end + + describe ".rsync_install" do + let(:cap) { caps.get(:rsync_install) } + + describe "successful installation" do + it "installs rsync" do + cap.rsync_install(machine) + expect(comm.received_commands[0]).to match(/pkg_add -I rsync/) + expect(comm.received_commands[1]).to match(/pkg_info/) + end + end + + describe "failure installation" do + before do + expect(comm).to receive(:execute).and_raise(Vagrant::Errors::RSyncNotInstalledInGuest, {command: '', output: ''}) + end + + it "raises custom exception" do + expect{ cap.rsync_install(machine) }.to raise_error(Vagrant::Errors::RSyncNotInstalledInGuest) + end + end + end + + describe ".rsync_installed" do + let(:cap) { caps.get(:rsync_installed) } + + it "checks if rsync is installed" do + comm.expect_command("which rsync") + cap.rsync_installed(machine) + end + end + + describe ".rsync_command" do + let(:cap) { caps.get(:rsync_command) } + + it "defaults to 'sudo rsync'" do + expect(cap.rsync_command(machine)).to eq("sudo rsync") + end + end +end diff --git a/test/unit/plugins/guests/photon/cap/configure_networks_test.rb b/test/unit/plugins/guests/photon/cap/configure_networks_test.rb index 97f66a727..bbcc717c7 100644 --- a/test/unit/plugins/guests/photon/cap/configure_networks_test.rb +++ b/test/unit/plugins/guests/photon/cap/configure_networks_test.rb @@ -45,7 +45,7 @@ describe "VagrantPlugins::GuestPhoton::Cap:ConfigureNetworks" do it "creates and starts the networks" do cap.configure_networks(machine, [network_1, network_2]) expect(comm.received_commands[1]).to match(/ifconfig eth1/) - expect(comm.received_commands[1]).to match(/ifconfig eth2 33.33.33.10 netmast 255.255.0.0/) + expect(comm.received_commands[1]).to match(/ifconfig eth2 33.33.33.10 netmask 255.255.0.0/) end end end diff --git a/test/unit/plugins/guests/redhat/cap/configure_networks_test.rb b/test/unit/plugins/guests/redhat/cap/configure_networks_test.rb index 1da032e88..926713298 100644 --- a/test/unit/plugins/guests/redhat/cap/configure_networks_test.rb +++ b/test/unit/plugins/guests/redhat/cap/configure_networks_test.rb @@ -56,9 +56,10 @@ describe "VagrantPlugins::GuestRedHat::Cap::ConfigureNetworks" do cap.configure_networks(machine, [network_1, network_2]) expect(comm.received_commands[0]).to match(/\/sbin\/ifdown 'eth1'/) - expect(comm.received_commands[0]).to match(/\/sbin\/ifup 'eth1'/) + expect(comm.received_commands[0]).to match(/ifcfg-eth1/) expect(comm.received_commands[0]).to match(/\/sbin\/ifdown 'eth2'/) - expect(comm.received_commands[0]).to match(/\/sbin\/ifup 'eth2'/) + expect(comm.received_commands[0]).to match(/ifcfg-eth2/) + expect(comm.received_commands[0]).to match(/nmcli c reload/) end end end diff --git a/test/unit/plugins/hosts/linux/cap/nfs_test.rb b/test/unit/plugins/hosts/linux/cap/nfs_test.rb new file mode 100644 index 000000000..144e9c8fd --- /dev/null +++ b/test/unit/plugins/hosts/linux/cap/nfs_test.rb @@ -0,0 +1,156 @@ +require_relative "../../../../base" +require_relative "../../../../../../plugins/hosts/linux/cap/nfs" +require_relative "../../../../../../lib/vagrant/util" + +describe VagrantPlugins::HostLinux::Cap::NFS do + + include_context "unit" + + let(:caps) do + VagrantPlugins::HostLinux::Plugin + .components + .host_capabilities[:linux] + end + + let(:tmp_exports_path) do + @tmp_exports ||= temporary_file + end + let(:exports_path){ VagrantPlugins::HostLinux::Cap::NFS::NFS_EXPORTS_PATH } + let(:env){ double(:env) } + let(:ui){ double(:ui) } + let(:host){ double(:host) } + + before do + @original_exports_path = VagrantPlugins::HostLinux::Cap::NFS::NFS_EXPORTS_PATH + VagrantPlugins::HostLinux::Cap::NFS.send(:remove_const, :NFS_EXPORTS_PATH) + VagrantPlugins::HostLinux::Cap::NFS.const_set(:NFS_EXPORTS_PATH, tmp_exports_path.to_s) + end + + after do + VagrantPlugins::HostLinux::Cap::NFS.send(:remove_const, :NFS_EXPORTS_PATH) + VagrantPlugins::HostLinux::Cap::NFS.const_set(:NFS_EXPORTS_PATH, @original_exports_path) + File.unlink(tmp_exports_path.to_s) if File.exist?(tmp_exports_path.to_s) + @tmp_exports = nil + end + + describe ".nfs_export" do + + let(:cap){ caps.get(:nfs_export) } + + before do + allow(env).to receive(:host).and_return(host) + allow(host).to receive(:capability).with(:nfs_apply_command).and_return("/bin/true") + allow(host).to receive(:capability).with(:nfs_check_command).and_return("/bin/true") + allow(host).to receive(:capability).with(:nfs_start_command).and_return("/bin/true") + allow(ui).to receive(:info) + allow(cap).to receive(:system).with("sudo /bin/true").and_return(true) + allow(cap).to receive(:system).with("/bin/true").and_return(true) + end + + it "should export new entries" do + cap.nfs_export(env, ui, SecureRandom.uuid, ["127.0.0.1"], "tmp" => {:hostpath => "/tmp"}) + exports_content = File.read(exports_path) + expect(exports_content).to match(/\/tmp.*127\.0\.0\.1/) + end + + it "should not remove existing entries" do + File.write(exports_path, "/custom/directory hostname1(rw,sync,no_subtree_check)") + cap.nfs_export(env, ui, SecureRandom.uuid, ["127.0.0.1"], "tmp" => {:hostpath => "/tmp"}) + exports_content = File.read(exports_path) + expect(exports_content).to match(/\/tmp.*127\.0\.0\.1/) + expect(exports_content).to match(/\/custom\/directory.*hostname1/) + end + + it "should remove entries no longer valid" do + valid_id = SecureRandom.uuid + other_id = SecureRandom.uuid + content =<<-EOH +# VAGRANT-BEGIN: #{Process.uid} #{other_id} +"/tmp" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=) +# VAGRANT-END: #{Process.uid} #{other_id} +# VAGRANT-BEGIN: #{Process.uid} #{valid_id} +"/var" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=) +# VAGRANT-END: #{Process.uid} #{valid_id} +EOH + File.write(exports_path, content) + cap.nfs_export(env, ui, valid_id, ["127.0.0.1"], "home" => {:hostpath => "/home"}) + exports_content = File.read(exports_path) + expect(exports_content).to include("/home") + expect(exports_content).to include("/tmp") + expect(exports_content).not_to include("/var") + end + end + + describe ".nfs_prune" do + + let(:cap){ caps.get(:nfs_prune) } + + before do + allow(ui).to receive(:info) + end + + it "should remove entries no longer valid" do + invalid_id = SecureRandom.uuid + valid_id = SecureRandom.uuid + content =<<-EOH +# VAGRANT-BEGIN: #{Process.uid} #{invalid_id} +"/tmp" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=) +# VAGRANT-END: #{Process.uid} #{invalid_id} +# VAGRANT-BEGIN: #{Process.uid} #{valid_id} +"/var" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=) +# VAGRANT-END: #{Process.uid} #{valid_id} +EOH + File.write(exports_path, content) + cap.nfs_prune(env, ui, [valid_id]) + exports_content = File.read(exports_path) + expect(exports_content).to include(valid_id) + expect(exports_content).not_to include(invalid_id) + expect(exports_content).to include("/var") + expect(exports_content).not_to include("/tmp") + end + end + + describe ".nfs_write_exports" do + + before do + File.write(tmp_exports_path, "original content") + end + + it "should write updated contents to file" do + described_class.nfs_write_exports("new content") + exports_content = File.read(exports_path) + expect(exports_content).to include("new content") + expect(exports_content).not_to include("original content") + end + + it "should only update contents if different" do + original_stat = File.stat(exports_path) + described_class.nfs_write_exports("original content") + updated_stat = File.stat(exports_path) + expect(original_stat).to eq(updated_stat) + end + + it "should retain existing file permissions" do + File.chmod(0600, exports_path) + original_stat = File.stat(exports_path) + described_class.nfs_write_exports("original content") + updated_stat = File.stat(exports_path) + expect(original_stat.mode).to eq(updated_stat.mode) + end + + it "should raise exception when failing to move new exports file" do + expect(Vagrant::Util::Subprocess).to receive(:execute).and_return( + Vagrant::Util::Subprocess::Result.new(1, "Failed to move file", "") + ) + expect{ described_class.nfs_write_exports("new content") }.to raise_error(Vagrant::Errors::NFSExportsFailed) + end + + it "should retain existing file owner and group IDs" do + pending("investigate using a simulated FS to test") + end + + it "should raise custom exception when chown fails" do + pending("investigate using a simulated FS to test") + end + end +end diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index bef729832..17d121ed7 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -133,6 +133,14 @@ describe VagrantPlugins::Kernel_V2::VMConfig do assert_valid end + + ["1", 1, "1.0", 1.0].each do |valid| + it "is valid: #{valid}" do + subject.box_version = valid + subject.finalize! + assert_valid + end + end end describe "#communicator" do @@ -526,6 +534,21 @@ describe VagrantPlugins::Kernel_V2::VMConfig do subject.finalize! assert_valid end + + it "allows providing custom name via options" do + subject.synced_folder(".", "/vagrant", name: "my-vagrant-folder") + sf = subject.synced_folders + expect(sf).to have_key("my-vagrant-folder") + expect(sf["my-vagrant-folder"][:guestpath]).to eq("/vagrant") + expect(sf["my-vagrant-folder"][:hostpath]).to eq(".") + end + + it "allows providing custom name without guest path" do + subject.synced_folder(".", name: "my-vagrant-folder") + sf = subject.synced_folders + expect(sf).to have_key("my-vagrant-folder") + expect(sf["my-vagrant-folder"][:hostpath]).to eq(".") + end end describe "#usable_port_range" do diff --git a/test/unit/plugins/provisioners/ansible/config/guest_test.rb b/test/unit/plugins/provisioners/ansible/config/guest_test.rb index c1c389686..a2b44a57b 100644 --- a/test/unit/plugins/provisioners/ansible/config/guest_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/guest_test.rb @@ -16,7 +16,8 @@ describe VagrantPlugins::Ansible::Config::Guest do let(:existing_file) { "this/path/is/a/stub" } it "supports a list of options" do - supported_options = %w( extra_vars + supported_options = %w( config_file + extra_vars galaxy_command galaxy_role_file galaxy_roles_path @@ -27,6 +28,7 @@ describe VagrantPlugins::Ansible::Config::Guest do inventory_path limit playbook + playbook_command provisioning_path raw_arguments skip_tags diff --git a/test/unit/plugins/provisioners/ansible/config/host_test.rb b/test/unit/plugins/provisioners/ansible/config/host_test.rb index f6871c209..e8d7bc27a 100644 --- a/test/unit/plugins/provisioners/ansible/config/host_test.rb +++ b/test/unit/plugins/provisioners/ansible/config/host_test.rb @@ -15,6 +15,7 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do it "supports a list of options" do supported_options = %w( ask_sudo_pass ask_vault_pass + config_file extra_vars force_remote_user galaxy_command @@ -26,6 +27,7 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do inventory_path limit playbook + playbook_command raw_arguments raw_ssh_args skip_tags @@ -63,7 +65,7 @@ describe VagrantPlugins::Ansible::Config::Host, :skip_windows => true do it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_sudo_pass, false end describe "ask_vault_pass option" do - it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_sudo_pass, false + it_behaves_like "any VagrantConfigProvisioner strict boolean attribute", :ask_vault_pass, false end describe "#validate" do diff --git a/test/unit/plugins/provisioners/ansible/config/shared.rb b/test/unit/plugins/provisioners/ansible/config/shared.rb index 7ff56ee91..92b2e0d90 100644 --- a/test/unit/plugins/provisioners/ansible/config/shared.rb +++ b/test/unit/plugins/provisioners/ansible/config/shared.rb @@ -3,6 +3,7 @@ shared_examples_for 'options shared by both Ansible provisioners' do it "assigns default values to unset common options" do subject.finalize! + expect(subject.config_file).to be_nil expect(subject.extra_vars).to be_nil expect(subject.galaxy_command).to eql("ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force") expect(subject.galaxy_role_file).to be_nil @@ -12,6 +13,7 @@ shared_examples_for 'options shared by both Ansible provisioners' do expect(subject.inventory_path).to be_nil expect(subject.limit).to be_nil expect(subject.playbook).to be_nil + expect(subject.playbook_command).to eql("ansible-playbook") expect(subject.raw_arguments).to be_nil expect(subject.skip_tags).to be_nil expect(subject.start_at_task).to be_nil diff --git a/test/unit/plugins/provisioners/ansible/provisioner_test.rb b/test/unit/plugins/provisioners/ansible/provisioner_test.rb index c26cecf8d..db562201d 100644 --- a/test/unit/plugins/provisioners/ansible/provisioner_test.rb +++ b/test/unit/plugins/provisioners/ansible/provisioner_test.rb @@ -59,8 +59,6 @@ VF stubbed_ui.stub(detail: "") machine.env.stub(ui: stubbed_ui) - subject.stub(:check_path) - config.playbook = 'playbook.yml' end @@ -195,7 +193,9 @@ VF before do unless example.metadata[:skip_before] config.finalize! + Vagrant::Util::Subprocess.stub(execute: Vagrant::Util::Subprocess::Result.new(0, "", "")) + subject.stub(:check_path) end end @@ -207,44 +207,46 @@ VF describe 'checking existence of Ansible configuration files' do - describe 'when the playbook file does not exist' do - it "raises an error", skip_before: true, skip_after: true do + STUBBED_INVALID_PATH = "/test/239nfmd/invalid_path".freeze - subject.stub(:check_path).and_raise(VagrantPlugins::Ansible::Errors::AnsibleError, - _key: :config_file_not_found, - config_option: "playbook", - path: "/home/wip/test/invalid_path.yml", - system: "host") + it 'raises an error when the `playbook` file does not exist', skip_before: true, skip_after: true do + subject.stub(:check_path).and_raise(VagrantPlugins::Ansible::Errors::AnsibleError, + _key: :config_file_not_found, + config_option: "playbook", + path: STUBBED_INVALID_PATH, + system: "host") - config.playbook = "/home/wip/test/invalid_path.yml" - config.finalize! + config.playbook = STUBBED_INVALID_PATH + config.finalize! - expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleError, - "`playbook` does not exist on the host: /home/wip/test/invalid_path.yml") + expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleError, + "`playbook` does not exist on the host: #{STUBBED_INVALID_PATH}") + end + + %w(config_file extra_vars inventory_path galaxy_role_file vault_password_file).each do |option_name| + it "raises an error when the '#{option_name}' does not exist", skip_before: true, skip_after: true do + Vagrant::Util::Subprocess.stub(execute: Vagrant::Util::Subprocess::Result.new(0, "", "")) + + config.playbook = existing_file + config.send(option_name + '=', STUBBED_INVALID_PATH) + if option_name == 'extra_vars' + # little trick to auto-append the '@' prefix, which is a duty of the config validator... + config.validate(machine) + end + config.finalize! + + expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleError, + "`#{option_name}` does not exist on the host: #{STUBBED_INVALID_PATH}") end end - describe 'when the inventory path does not exist' do - it "raises an error" - end - - describe 'when the extra_vars file does not exist' do - it "raises an error" - end - - describe 'when the galaxy_role_file does not exist' do - it "raises an error" - end - - describe 'when the vault_password_file does not exist' do - it "raises an error" - end - end describe 'when ansible-playbook fails' do it "raises an error", skip_before: true, skip_after: true do config.finalize! + + subject.stub(:check_path) Vagrant::Util::Subprocess.stub(execute: Vagrant::Util::Subprocess::Result.new(1, "", "")) expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed) @@ -273,6 +275,18 @@ VF end end + describe "with playbook_command option" do + before do + config.playbook_command = "custom-ansible-playbook" + end + + it "uses custom playbook_command to run playbooks" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + expect(args[0]).to eq("custom-ansible-playbook") + } + end + end + describe "with host_vars option" do it_should_create_and_use_generated_inventory @@ -570,6 +584,20 @@ VF end end + context "with config_file option defined" do + before do + config.config_file = existing_file + end + + it "sets ANSIBLE_CONFIG environment variable" do + expect(Vagrant::Util::Subprocess).to receive(:execute).with { |*args| + cmd_opts = args.last + expect(cmd_opts[:env]).to include("ANSIBLE_CONFIG") + expect(cmd_opts[:env]['ANSIBLE_CONFIG']).to eql(existing_file) + } + end + end + describe "with ask_vault_pass option" do before do config.ask_vault_pass = true @@ -765,6 +793,8 @@ VF it "raises an error when ansible-galaxy command fails", skip_before: true, skip_after: true do config.finalize! + + subject.stub(:check_path) Vagrant::Util::Subprocess.stub(execute: Vagrant::Util::Subprocess::Result.new(1, "", "")) expect {subject.provision}.to raise_error(VagrantPlugins::Ansible::Errors::AnsibleCommandFailed) @@ -840,11 +870,12 @@ VF config.raw_arguments = ["--why-not", "--su-user=foot", "--ask-su-pass", "--limit=all", "--private-key=./myself.key", "--extra-vars='{\"var3\":\"foo\"}'"] # environment variables + config.config_file = existing_file config.host_key_checking = true config.raw_ssh_args = ['-o ControlMaster=no'] end - it_should_set_arguments_and_environment_variables 21, 5, true + it_should_set_arguments_and_environment_variables 21, 6, true it_should_explicitly_enable_ansible_ssh_control_persist_defaults it_should_set_optional_arguments({ "extra_vars" => "--extra-vars={\"var1\":\"string with 'apostrophes', \\\\, \\\" and =\",\"var2\":{\"x\":42}}", "sudo" => "--sudo", @@ -871,7 +902,7 @@ VF it "shows the ansible-playbook command, with additional quotes when required" do expect(machine.env.ui).to receive(:detail).with { |full_command| - expect(full_command).to eq(%Q(PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_ROLES_PATH='/up/to the stars' ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -i '/my/key1' -i '/my/key2' -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit="machine*:&vagrant:!that_one" --inventory-file=#{generated_inventory_dir} --extra-vars="{\\"var1\\":\\"string with 'apostrophes', \\\\\\\\, \\\\\\" and =\\",\\"var2\\":{\\"x\\":42}}" --sudo --sudo-user=deployer -vvv --vault-password-file=#{File.expand_path(__FILE__)} --tags=db,www --skip-tags=foo,bar --start-at-task="joe's awesome task" --why-not --su-user=foot --ask-su-pass --limit=all --private-key=./myself.key --extra-vars='{\"var3\":\"foo\"}' playbook.yml)) + expect(full_command).to eq(%Q(PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_ROLES_PATH='/up/to the stars' ANSIBLE_CONFIG='#{existing_file}' ANSIBLE_HOST_KEY_CHECKING=true ANSIBLE_SSH_ARGS='-o IdentitiesOnly=yes -i '/my/key1' -i '/my/key2' -o ForwardAgent=yes -o ControlMaster=no -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --connection=ssh --timeout=30 --ask-sudo-pass --ask-vault-pass --limit="machine*:&vagrant:!that_one" --inventory-file=#{generated_inventory_dir} --extra-vars="{\\"var1\\":\\"string with 'apostrophes', \\\\\\\\, \\\\\\" and =\\",\\"var2\\":{\\"x\\":42}}" --sudo --sudo-user=deployer -vvv --vault-password-file=#{existing_file} --tags=db,www --skip-tags=foo,bar --start-at-task="joe's awesome task" --why-not --su-user=foot --ask-su-pass --limit=all --private-key=./myself.key --extra-vars='{\"var3\":\"foo\"}' playbook.yml)) } end end diff --git a/test/unit/plugins/provisioners/chef/config/base_test.rb b/test/unit/plugins/provisioners/chef/config/base_test.rb index c5b207565..a12c76295 100644 --- a/test/unit/plugins/provisioners/chef/config/base_test.rb +++ b/test/unit/plugins/provisioners/chef/config/base_test.rb @@ -63,14 +63,6 @@ describe VagrantPlugins::Chef::Config::Base do end end - describe "#prerelease" do - it "should not exist in Vagrant 1.9" do - if Vagrant::VERSION >= "1.9" - raise "This option should be removed!" - end - end - end - describe "#version" do it "defaults to :latest" do subject.finalize! diff --git a/test/unit/plugins/provisioners/docker/config_test.rb b/test/unit/plugins/provisioners/docker/config_test.rb index ba2b6836d..3f3bfb15a 100644 --- a/test/unit/plugins/provisioners/docker/config_test.rb +++ b/test/unit/plugins/provisioners/docker/config_test.rb @@ -137,12 +137,4 @@ describe VagrantPlugins::DockerProvisioner::Config do }) end end - - describe "#version" do - it "is removed in Vagrant 1.9" do - if Vagrant::VERSION >= "1.9" - raise "Remove deprecated option" - end - end - end end diff --git a/test/unit/plugins/provisioners/salt/config_test.rb b/test/unit/plugins/provisioners/salt/config_test.rb index 92e935db0..24bf24796 100644 --- a/test/unit/plugins/provisioners/salt/config_test.rb +++ b/test/unit/plugins/provisioners/salt/config_test.rb @@ -16,12 +16,6 @@ describe VagrantPlugins::Salt::Config do let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } - describe "#config_dir" do - it "is deprecated until Vagrant 1.9" do - raise "Remove the deprecation" if Vagrant::VERSION >= "1.9.0" - end - end - describe "validate" do let(:error_key) { "salt provisioner" } diff --git a/test/unit/plugins/provisioners/salt/provisioner_test.rb b/test/unit/plugins/provisioners/salt/provisioner_test.rb new file mode 100644 index 000000000..93ce44a87 --- /dev/null +++ b/test/unit/plugins/provisioners/salt/provisioner_test.rb @@ -0,0 +1,35 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/provisioners/salt/provisioner") + +describe VagrantPlugins::Salt::Provisioner do + include_context "unit" + + subject { described_class.new(machine, config) } + + 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 + + let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) } + let(:config) { double("config") } + let(:communicator) { double("comm") } + let(:guest) { double("guest") } + + before do + machine.stub(communicate: communicator) + machine.stub(guest: guest) + + communicator.stub(execute: true) + communicator.stub(upload: true) + + guest.stub(capability?: false) + end + + describe "#provision" do + + end +end diff --git a/test/unit/plugins/provisioners/shell/provisioner_test.rb b/test/unit/plugins/provisioners/shell/provisioner_test.rb index 8f83f2554..ef977fb2f 100644 --- a/test/unit/plugins/provisioners/shell/provisioner_test.rb +++ b/test/unit/plugins/provisioners/shell/provisioner_test.rb @@ -2,14 +2,19 @@ require File.expand_path("../../../../base", __FILE__) require Vagrant.source_root.join("plugins/provisioners/shell/provisioner") describe "Vagrant::Shell::Provisioner" do + include_context "unit" + let(:env){ isolated_environment } let(:machine) { - double(:machine).tap { |machine| + double(:machine, env: env, id: "ID").tap { |machine| machine.stub_chain(:config, :vm, :communicator).and_return(:not_winrm) machine.stub_chain(:communicate, :tap) {} } } + before do + allow(env).to receive(:tmp_path).and_return(Pathname.new("/dev/null")) + end context "with a script that contains invalid us-ascii byte sequences" do let(:config) { @@ -37,4 +42,67 @@ describe "Vagrant::Shell::Provisioner" do }.not_to raise_error end end + + context "with remote script" do + + context "that does not have matching sha1 checksum" do + let(:config) { + double( + :config, + :args => "doesn't matter", + :env => {}, + :upload_path => "arbitrary", + :remote? => true, + :path => "http://example.com/script.sh", + :binary => false, + :md5 => nil, + :sha1 => 'EXPECTED_VALUE' + ) + } + + let(:digest){ double("digest") } + before do + Vagrant::Util::Downloader.any_instance.should_receive(:execute_curl).and_return(true) + allow(digest).to receive(:file).and_return(digest) + expect(Digest::SHA1).to receive(:new).and_return(digest) + expect(digest).to receive(:hexdigest).and_return('INVALID_VALUE') + end + + it "should raise an exception" do + vsp = VagrantPlugins::Shell::Provisioner.new(machine, config) + + expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError) + end + end + + context "that does not have matching md5 checksum" do + let(:config) { + double( + :config, + :args => "doesn't matter", + :env => {}, + :upload_path => "arbitrary", + :remote? => true, + :path => "http://example.com/script.sh", + :binary => false, + :md5 => 'EXPECTED_VALUE', + :sha1 => nil + ) + } + + let(:digest){ double("digest") } + before do + Vagrant::Util::Downloader.any_instance.should_receive(:execute_curl).and_return(true) + allow(digest).to receive(:file).and_return(digest) + expect(Digest::MD5).to receive(:new).and_return(digest) + expect(digest).to receive(:hexdigest).and_return('INVALID_VALUE') + end + + it "should raise an exception" do + vsp = VagrantPlugins::Shell::Provisioner.new(machine, config) + + expect{ vsp.provision }.to raise_error(Vagrant::Errors::DownloaderChecksumError) + end + end + end end diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index f2c9cf9bc..15d0a3e0e 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -866,6 +866,30 @@ VF end end + it "is the provider in the Vagrantfile that is usable even if only one specified (1)" do + subject.vagrantfile.config.vm.provider "foo" + subject.vagrantfile.config.vm.finalize! + + plugin_providers[:foo] = [provider_usable_class(true), { priority: 5 }] + plugin_providers[:bar] = [provider_usable_class(true), { priority: 7 }] + + with_temp_env("VAGRANT_DEFAULT_PROVIDER" => nil) do + expect(subject.default_provider).to eq(:foo) + end + end + + it "is the provider in the Vagrantfile that is usable even if only one specified (2)" do + subject.vagrantfile.config.vm.provider "bar" + subject.vagrantfile.config.vm.finalize! + + plugin_providers[:foo] = [provider_usable_class(true), { priority: 7 }] + plugin_providers[:bar] = [provider_usable_class(true), { priority: 5 }] + + with_temp_env("VAGRANT_DEFAULT_PROVIDER" => nil) do + expect(subject.default_provider).to eq(:bar) + end + end + it "is the highest usable provider outside the Vagrantfile" do subject.vagrantfile.config.vm.provider "foo" subject.vagrantfile.config.vm.finalize! diff --git a/test/unit/vagrant/plugin/manager_test.rb b/test/unit/vagrant/plugin/manager_test.rb index ea8764b06..da94c6ea6 100644 --- a/test/unit/vagrant/plugin/manager_test.rb +++ b/test/unit/vagrant/plugin/manager_test.rb @@ -34,6 +34,7 @@ describe Vagrant::Plugin::Manager do expect(plugins).to have_key("foo") expect(local).to be_false }.and_return(specs) + expect(bundler).to receive(:clean) result = subject.install_plugin("foo") @@ -45,14 +46,14 @@ describe Vagrant::Plugin::Manager do end it "masks GemNotFound with our error" do - expect(bundler).to receive(:install).and_raise(Bundler::GemNotFound) + expect(bundler).to receive(:install).and_raise(Gem::GemNotFoundException) expect { subject.install_plugin("foo") }. to raise_error(Vagrant::Errors::PluginGemNotFound) end it "masks bundler errors with our own error" do - expect(bundler).to receive(:install).and_raise(Bundler::InstallError) + expect(bundler).to receive(:install).and_raise(Gem::InstallError) expect { subject.install_plugin("foo") }. to raise_error(Vagrant::Errors::BundlerError) @@ -66,14 +67,11 @@ describe Vagrant::Plugin::Manager do local_spec.name = "bar" local_spec.version = version - expect(bundler).to receive(:install_local).with(name). + expect(bundler).to receive(:install_local).with(name, {}). ordered.and_return(local_spec) - expect(bundler).to receive(:install).once.with { |plugins, local| - expect(plugins).to have_key("bar") - expect(plugins["bar"]["gem_version"]).to eql("#{version}") - expect(local).to be_true - }.ordered.and_return([local_spec]) + expect(bundler).not_to receive(:install) + expect(bundler).to receive(:clean) subject.install_plugin(name) @@ -99,6 +97,7 @@ describe Vagrant::Plugin::Manager do expect(plugins["foo"]["gem_version"]).to eql(">= 0.1.0") expect(local).to be_false }.and_return(specs) + expect(bundler).to receive(:clean) subject.install_plugin("foo", version: ">= 0.1.0") @@ -113,6 +112,7 @@ describe Vagrant::Plugin::Manager do expect(plugins["foo"]["gem_version"]).to eql("0.1.0") expect(local).to be_false }.and_return(specs) + expect(bundler).to receive(:clean) subject.install_plugin("foo", version: "0.1.0") @@ -140,7 +140,7 @@ describe Vagrant::Plugin::Manager do end it "masks bundler errors with our own error" do - expect(bundler).to receive(:clean).and_raise(Bundler::InstallError) + expect(bundler).to receive(:clean).and_raise(Gem::InstallError) expect { subject.uninstall_plugin("foo") }. to raise_error(Vagrant::Errors::BundlerError) @@ -182,7 +182,7 @@ describe Vagrant::Plugin::Manager do describe "#update_plugins" do it "masks bundler errors with our own error" do - expect(bundler).to receive(:update).and_raise(Bundler::InstallError) + expect(bundler).to receive(:update).and_raise(Gem::InstallError) expect { subject.update_plugins([]) }. to raise_error(Vagrant::Errors::BundlerError) diff --git a/test/unit/vagrant/plugin/state_file_test.rb b/test/unit/vagrant/plugin/state_file_test.rb index a0fbccf66..ec50f3cba 100644 --- a/test/unit/vagrant/plugin/state_file_test.rb +++ b/test/unit/vagrant/plugin/state_file_test.rb @@ -26,11 +26,12 @@ describe Vagrant::Plugin::StateFile do plugins = instance.installed_plugins expect(plugins.length).to eql(1) expect(plugins["foo"]).to eql({ - "ruby_version" => RUBY_VERSION, - "vagrant_version" => Vagrant::VERSION, - "gem_version" => "", - "require" => "", - "sources" => [], + "ruby_version" => RUBY_VERSION, + "vagrant_version" => Vagrant::VERSION, + "gem_version" => "", + "require" => "", + "sources" => [], + "installed_gem_version" => nil, }) end diff --git a/test/unit/vagrant/util/downloader_test.rb b/test/unit/vagrant/util/downloader_test.rb index f89c6cbe9..5a9bf2f2f 100644 --- a/test/unit/vagrant/util/downloader_test.rb +++ b/test/unit/vagrant/util/downloader_test.rb @@ -93,6 +93,104 @@ describe Vagrant::Util::Downloader do expect(subject.download!).to be_true end end + + context "with checksum" do + let(:checksum_expected_value){ 'MD5_CHECKSUM_VALUE' } + let(:checksum_invalid_value){ 'INVALID_VALUE' } + let(:digest){ double("digest") } + + before do + allow(digest).to receive(:file).and_return(digest) + end + + [Digest::MD5, Digest::SHA1].each do |klass| + short_name = klass.to_s.split("::").last.downcase + + context "using #{short_name} digest" do + subject { described_class.new(source, destination, short_name.to_sym => checksum_expected_value) } + + context "that matches expected value" do + before do + expect(klass).to receive(:new).and_return(digest) + expect(digest).to receive(:hexdigest).and_return(checksum_expected_value) + end + + it "should not raise an exception" do + expect(subject.download!).to be_true + end + end + + context "that does not match expected value" do + before do + expect(klass).to receive(:new).and_return(digest) + expect(digest).to receive(:hexdigest).and_return(checksum_invalid_value) + end + + it "should raise an exception" do + expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError) + end + end + end + end + + context "using both md5 and sha1 digests" do + context "that both match expected values" do + subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_expected_value) } + + before do + expect(Digest::MD5).to receive(:new).and_return(digest) + expect(Digest::SHA1).to receive(:new).and_return(digest) + expect(digest).to receive(:hexdigest).and_return(checksum_expected_value).exactly(2).times + end + + it "should not raise an exception" do + expect(subject.download!).to be_true + end + end + + context "that only sha1 matches expected value" do + subject { described_class.new(source, destination, md5: checksum_invalid_value, sha1: checksum_expected_value) } + + before do + allow(Digest::MD5).to receive(:new).and_return(digest) + allow(Digest::SHA1).to receive(:new).and_return(digest) + expect(digest).to receive(:hexdigest).and_return(checksum_expected_value).at_least(:once) + end + + it "should raise an exception" do + expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError) + end + end + + context "that only md5 matches expected value" do + subject { described_class.new(source, destination, md5: checksum_expected_value, sha1: checksum_invalid_value) } + + before do + allow(Digest::MD5).to receive(:new).and_return(digest) + allow(Digest::SHA1).to receive(:new).and_return(digest) + expect(digest).to receive(:hexdigest).and_return(checksum_expected_value).at_least(:once) + end + + it "should raise an exception" do + expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError) + end + end + + context "that none match expected value" do + subject { described_class.new(source, destination, md5: checksum_invalid_value, sha1: checksum_invalid_value) } + + before do + allow(Digest::MD5).to receive(:new).and_return(digest) + allow(Digest::SHA1).to receive(:new).and_return(digest) + expect(digest).to receive(:hexdigest).and_return(checksum_expected_value).at_least(:once) + end + + it "should raise an exception" do + expect{ subject.download! }.to raise_error(Vagrant::Errors::DownloaderChecksumError) + end + end + end + end end describe "#head" do diff --git a/test/unit/vagrant/util/subprocess_test.rb b/test/unit/vagrant/util/subprocess_test.rb new file mode 100644 index 000000000..3934d1a3c --- /dev/null +++ b/test/unit/vagrant/util/subprocess_test.rb @@ -0,0 +1,50 @@ +require File.expand_path("../../../base", __FILE__) +require "vagrant/util/subprocess" + +describe Vagrant::Util::Subprocess do + describe '#execute' do + before do + # ensure we have `cat` and `echo` in our PATH so that we can run these + # tests successfully. + ['cat', 'echo'].each do |cmd| + if !Vagrant::Util::Which.which(cmd) + pending("cannot run subprocess tests without command #{cmd.inspect}") + end + end + end + + let (:cat) { described_class.new('cat', :notify => [:stdin]) } + + it 'yields the STDIN stream for the process if we set :notify => :stdin' do + echo = described_class.new('echo', 'hello world', :notify => [:stdin]) + echo.execute do |type, data| + expect(type).to eq(:stdin) + expect(data).to be_a(::IO) + end + end + + it 'can close STDIN' do + result = cat.execute do |type, stdin| + # We should be able to close STDIN without raising an exception + stdin.close + end + + # we should exit successfully. + expect(result.exit_code).to eq(0) + end + + it 'can write to STDIN correctly' do + data = "hello world\n" + result = cat.execute do |type, stdin| + stdin.write(data) + stdin.close + end + + # we should exit successfully. + expect(result.exit_code).to eq(0) + + # we should see our data as the output from `cat` + expect(result.stdout).to eq(data) + end + end +end diff --git a/vagrant.gemspec b/vagrant.gemspec index 92d0a0bb7..3c74eb161 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -16,11 +16,6 @@ Gem::Specification.new do |s| s.required_rubygems_version = ">= 1.3.6" s.rubyforge_project = "vagrant" - # Do not update the Bundler constraint. Vagrant relies on internal Bundler - # APIs, so even point releases can introduce breaking changes. These changes - # are *untestable* until after a release is made because there is no way for - # Bundler to exec into itself. Please do not update the Bundler constraint. - s.add_dependency "bundler", "= 1.12.5" s.add_dependency "childprocess", "~> 0.5.0" s.add_dependency "erubis", "~> 2.7.0" s.add_dependency "i18n", ">= 0.6.0", "<= 0.8.0" @@ -45,7 +40,9 @@ Gem::Specification.new do |s| # tighter restrictions on valid ruby versions s.add_dependency "ruby_dep", "<= 1.3.1" - s.add_development_dependency "rake" + # Constraint rake to properly handle deprecated method usage + # from within rspec + s.add_development_dependency "rake", "~> 11.3.0" s.add_development_dependency "rspec", "~> 2.14.0" s.add_development_dependency "webmock", "~> 1.20" s.add_development_dependency "fake_ftp", "~> 0.1" diff --git a/version.txt b/version.txt index f263cd11b..9ab8337f3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.8.6 +1.9.1 diff --git a/website/Gemfile b/website/Gemfile index 2a2ce09d6..be3179745 100644 --- a/website/Gemfile +++ b/website/Gemfile @@ -1,4 +1,3 @@ source "https://rubygems.org" -gem "middleman-hashicorp", - git: "https://github.com/hashicorp/middleman-hashicorp.git" +gem "middleman-hashicorp", "0.3.4" diff --git a/website/Gemfile.lock b/website/Gemfile.lock index be1324414..f584f9cb6 100644 --- a/website/Gemfile.lock +++ b/website/Gemfile.lock @@ -1,23 +1,3 @@ -GIT - remote: https://github.com/hashicorp/middleman-hashicorp.git - revision: 80ddc227b26cbbb3742d14396f26172174222080 - specs: - middleman-hashicorp (0.2.0) - bootstrap-sass (~> 3.3) - builder (~> 3.2) - less (~> 2.6) - middleman (~> 3.4) - middleman-livereload (~> 3.4) - middleman-minify-html (~> 3.4) - middleman-syntax (~> 2.0) - rack-contrib (~> 1.2) - rack-protection (~> 1.5) - rack-rewrite (~> 1.5) - rack-ssl-enforcer (~> 0.2) - redcarpet (~> 3.2) - therubyracer (~> 0.12) - thin (~> 1.6) - GEM remote: https://rubygems.org/ specs: @@ -27,7 +7,7 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - autoprefixer-rails (6.4.1.1) + autoprefixer-rails (6.5.1.1) execjs bootstrap-sass (3.3.7) autoprefixer-rails (>= 5.2.1) @@ -44,7 +24,6 @@ GEM coffee-script-source execjs coffee-script-source (1.10.0) - commonjs (0.2.7) compass (1.0.3) chunky_png (~> 1.2) compass-core (~> 1.0.2) @@ -57,7 +36,6 @@ GEM sass (>= 3.3.0, < 3.5) compass-import-once (1.0.5) sass (>= 3.2, < 3.5) - daemons (1.2.4) em-websocket (0.5.1) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) @@ -65,20 +43,15 @@ GEM eventmachine (1.2.0.1) execjs (2.7.0) ffi (1.9.14) - git-version-bump (0.15.1) haml (4.0.7) tilt hike (1.2.3) hooks (0.4.1) uber (~> 0.0.14) - htmlcompressor (0.2.0) http_parser.rb (0.6.0) i18n (0.7.0) json (1.8.3) kramdown (1.12.0) - less (2.6.0) - commonjs (~> 0.2.7) - libv8 (3.16.14.15) listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -105,55 +78,49 @@ GEM rack (>= 1.4.5, < 2.0) thor (>= 0.15.2, < 2.0) tilt (~> 1.4.1, < 2.0) + middleman-hashicorp (0.3.4) + bootstrap-sass (~> 3.3) + builder (~> 3.2) + middleman (~> 3.4) + middleman-livereload (~> 3.4) + middleman-syntax (~> 3.0) + redcarpet (~> 3.3) middleman-livereload (3.4.6) em-websocket (~> 0.5.1) middleman-core (>= 3.3) rack-livereload (~> 0.3.15) - middleman-minify-html (3.4.1) - htmlcompressor (~> 0.2.0) - middleman-core (>= 3.2) middleman-sprockets (3.5.0) middleman-core (>= 3.3) sprockets (~> 2.12.1) sprockets-helpers (~> 1.1.0) sprockets-sass (~> 1.3.0) - middleman-syntax (2.1.0) + middleman-syntax (3.0.0) middleman-core (>= 3.2) - rouge (~> 1.0) + rouge (~> 2.0) mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) mini_portile2 (2.1.0) minitest (5.9.1) multi_json (1.12.1) - nokogiri (1.6.8) + nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) padrino-helpers (0.12.8.1) i18n (~> 0.6, >= 0.6.7) padrino-support (= 0.12.8.1) tilt (~> 1.4.1) padrino-support (0.12.8.1) activesupport (>= 3.1) - pkg-config (1.1.7) rack (1.6.4) - rack-contrib (1.4.0) - git-version-bump (~> 0.15) - rack (~> 1.4) rack-livereload (0.3.16) rack - rack-protection (1.5.3) - rack - rack-rewrite (1.5.1) - rack-ssl-enforcer (0.2.9) rack-test (0.6.3) rack (>= 1.0) - rb-fsevent (0.9.7) + rb-fsevent (0.9.8) rb-inotify (0.9.7) ffi (>= 0.5.0) redcarpet (3.3.4) - ref (2.0.0) - rouge (1.11.1) + rouge (2.0.6) sass (3.4.22) sprockets (2.12.4) hike (~> 1.2) @@ -165,13 +132,6 @@ GEM sprockets-sass (1.3.1) sprockets (~> 2.0) tilt (~> 1.1) - therubyracer (0.12.2) - libv8 (~> 3.16.14.0) - ref - thin (1.7.0) - daemons (~> 1.0, >= 1.0.9) - eventmachine (~> 1.0, >= 1.0.4) - rack (>= 1, < 3) thor (0.19.1) thread_safe (0.3.5) tilt (1.4.1) @@ -188,7 +148,7 @@ PLATFORMS ruby DEPENDENCIES - middleman-hashicorp! + middleman-hashicorp (= 0.3.4) BUNDLED WITH - 1.13.1 + 1.13.6 diff --git a/website/Makefile b/website/Makefile index 63bb4cab1..a97e8b4c7 100644 --- a/website/Makefile +++ b/website/Makefile @@ -1,10 +1,14 @@ -all: build +VERSION?="0.3.4" -init: - bundle +website: + @echo "==> Starting website in Docker..." + @docker run \ + --interactive \ + --rm \ + --tty \ + --publish "4567:4567" \ + --publish "35729:35729" \ + --volume "$(shell pwd):/website" \ + hashicorp/middleman-hashicorp:${VERSION} -dev: init - bundle exec middleman server - -build: init - bundle exec middleman build \ No newline at end of file +.PHONY: website diff --git a/website/README.md b/website/README.md index f8730c3db..5e4f1d11a 100644 --- a/website/README.md +++ b/website/README.md @@ -1,20 +1,30 @@ -# VagrantUp.com +# Vagrant Website -This is the repository for the [Vagrant website](https://www.vagrantup.com). - -This is a [Middleman](https://middlemanapp.com) project, which builds a static -site from these source files. The site is hosted fronted by -[Fastly](https://www.fastly.com). +This subdirectory contains the entire source for the [Vagrant Website](https://www.vagrantup.com/). +This is a [Middleman](http://middlemanapp.com) project, which builds a static +site from these source files. ## Contributions Welcome! If you find a typo or you feel like you can improve the HTML, CSS, or JavaScript, we welcome contributions. Feel free to open issues or pull -requests like any normal GitHub project, and we will merge it in. +requests like any normal GitHub project, and we'll merge it in. ## Running the Site Locally -Running the site locally is simple. Clone this repo and run `make dev`. +To run the site locally, clone this repository and run: -Then open up `localhost:4567`. Note that some URLs you may need to append -".html" to make them work (in the navigation and such). +```shell +$ make website +``` + +You must have Docker installed for this to work. + +Alternatively, you can manually run the website like this: + +```shell +$ bundle +$ bundle exec middleman server +``` + +Then open up `http://localhost:4567`. diff --git a/website/config.rb b/website/config.rb index 5f8231c81..9aae22367 100644 --- a/website/config.rb +++ b/website/config.rb @@ -2,7 +2,7 @@ set :base_url, "https://www.vagrantup.com/" activate :hashicorp do |h| h.name = "vagrant" - h.version = "1.8.5" + h.version = "1.9.0" h.github_slug = "mitchellh/vagrant" end @@ -56,4 +56,13 @@ helpers do "Vagrant by HashiCorp" end + + # Get the description for the page + # + # @param [Middleman::Page] page + # + # @return [String] + def description_for(page) + return escape_html(current_page.data.description || "") + end end diff --git a/website/packer.json b/website/packer.json index 5732112c3..b05aecaa5 100644 --- a/website/packer.json +++ b/website/packer.json @@ -8,15 +8,16 @@ "builders": [ { "type": "docker", - "image": "ruby:2.3-slim", - "commit": "true" + "image": "hashicorp/middleman-hashicorp:0.3.4", + "discard": "true", + "run_command": ["-d", "-i", "-t", "{{ .Image }}", "/bin/sh"] } ], "provisioners": [ { "type": "file", "source": ".", - "destination": "/app" + "destination": "/website" }, { "type": "shell", @@ -27,16 +28,9 @@ "FASTLY_API_KEY={{ user `fastly_api_key` }}" ], "inline": [ - "apt-get -qq update", - "apt-get -yqq install build-essential curl git libffi-dev wget", - "apt-get -yqq install python-pip", - "pip install s3cmd", - "cd /app", - - "bundle check || bundle install --jobs 7", + "bundle check || bundle install", "bundle exec middleman build", - - "/bin/bash ./scripts/deploy.sh" + "/bin/sh ./scripts/deploy.sh" ] } ] diff --git a/website/scripts/deploy.sh b/website/scripts/deploy.sh index 595223d7d..362e8d115 100755 --- a/website/scripts/deploy.sh +++ b/website/scripts/deploy.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh set -e PROJECT="vagrant" @@ -28,17 +28,14 @@ if ! command -v "s3cmd" >/dev/null 2>&1; then exit 1 fi -# Get the parent directory of where this script is and change into our website -# directory -SOURCE="${BASH_SOURCE[0]}" -while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done -DIR="$(cd -P "$( dirname "$SOURCE" )/.." && pwd)" +# Get the parent directory of where this script is and cd there +DIR="$(cd "$(dirname "$(readlink -f "$0")")/.." && pwd)" # Delete any .DS_Store files for our OS X friends. find "$DIR" -type f -name '.DS_Store' -delete # Upload the files to S3 - we disable mime-type detection by the python library -# and just guess from the file extension because it is surprisingly more +# and just guess from the file extension because it's surprisingly more # accurate, especially for CSS and javascript. We also tag the uploaded files # with the proper Surrogate-Key, which we will later purge in our API call to # Fastly. @@ -106,10 +103,16 @@ fi # Warm the cache with recursive wget. if [ -z "$NO_WARM" ]; then echo "Warming Fastly cache..." + echo "" + echo "If this step fails, there are likely missing or broken assets or links" + echo "on the website. Run the following command manually on your laptop, and" + echo "search for \"ERROR\" in the output:" + echo "" + echo "wget --recursive --delete-after https://$PROJECT_URL/" + echo "" wget \ --recursive \ --delete-after \ - --level 0 \ --quiet \ "https://$PROJECT_URL/" fi diff --git a/website/source/docs/boxes/versioning.html.md b/website/source/docs/boxes/versioning.html.md index 717d7ec8c..88feee9ae 100644 --- a/website/source/docs/boxes/versioning.html.md +++ b/website/source/docs/boxes/versioning.html.md @@ -100,6 +100,8 @@ user has to manually enter a command to do it. Vagrant does not automatically prune old versions because it does not know if they might be in use by other Vagrant environments. Because boxes can -be large, you may want to actively prune them once in awhile using +be large, you may want to actively prune them once in a while using `vagrant box remove`. You can see all the boxes that are installed using `vagrant box list`. + +Another option is to use `vagrant box prune` command to remove all installed boxes that are outdated and not currently in use. \ No newline at end of file diff --git a/website/source/docs/cli/box.html.md b/website/source/docs/cli/box.html.md index 5320c1c99..c2da385d4 100644 --- a/website/source/docs/cli/box.html.md +++ b/website/source/docs/cli/box.html.md @@ -150,6 +150,24 @@ with the `--all` flag. name. This is only required if a box is backed by multiple providers. If there is only a single provider, Vagrant will default to removing it. + +# Box prune + +**Command: `vagrant box prune`** + +This command removes old versions of installed boxes. If the box in currently in use vagrant will ask you if you to confirm. + +## Options + +* `--provider PROVIDER` - The specific provider type for the boxes to destroy. + +* `--dry-run` - Only print the boxes that would be removed. + +* `--name NAME` - The specific box name to check for outdated versions. + +* `--force` - Destroy without confirmation even when box is in use. + + # Box Repackage **Command: `vagrant box repackage NAME PROVIDER VERSION`** diff --git a/website/source/docs/cli/plugin.html.md b/website/source/docs/cli/plugin.html.md index 958faca2a..42d3eefdb 100644 --- a/website/source/docs/cli/plugin.html.md +++ b/website/source/docs/cli/plugin.html.md @@ -16,11 +16,36 @@ This is the command used to manage [plugins](/docs/plugins/). The main functionality of this command is exposed via another level of subcommands: -* `install` -* `license` -* `list` -* `uninstall` -* `update` +* [`expunge`](#plugin-expunge) +* [`install`](#plugin-install) +* [`license`](#plugin-license) +* [`list`](#plugin-list) +* [`repair`](#plugin-repair) +* [`uninstall`](#plugin-uninstall) +* [`update`](#plugin-update) + +# Plugin Expunge + +**Command: `vagrant plugin expunge`** + +This removes all user installed plugin information. All plugin gems, their +dependencies, and the `plugins.json` file are removed. This command +provides a simple mechanism to fully remove all user installed custom plugins. + +When upgrading Vagrant it may be required to reinstall plugins due to +an internal incompatibility. The expunge command can help make that process +easier by attempting to automatically reinstall currently configured +plugins: + +```shell +# Delete all plugins and reinstall +$ vagrant plugin expunge --reinstall +``` + +This command accepts optional command-line flags: + +* `--force` - Do not prompt for confirmation prior to removal +* `--reinstall` - Attempt to reinstall plugins after removal # Plugin Install @@ -86,6 +111,16 @@ If a version constraint was specified for a plugin when installing it, the constraint will be listed as well. Other plugin-specific information may be shown, too. +# Plugin Repair + +Vagrant may fail to properly initialize user installed custom plugins. This can +be caused my improper plugin installation/removal, or by manual manipluation of +plugin related files like the `plugins.json` data file. Vagrant can attempt +to automatically repair the problem. + +If automatic repair is not successful, refer to the [expunge](#plugin-expunge) +command + # Plugin Uninstall **Command: `vagrant plugin uninstall [ ...]`** diff --git a/website/source/docs/cli/reload.html.md b/website/source/docs/cli/reload.html.md index 587d0825c..724fbf452 100644 --- a/website/source/docs/cli/reload.html.md +++ b/website/source/docs/cli/reload.html.md @@ -27,5 +27,5 @@ the provisioners to re-run by specifying the `--provision` flag. * `--provision-with x,y,z` - This will only run the given provisioners. For example, if you have a `:shell` and `:chef_solo` provisioner and run - `vagrant provision --provision-with shell`, only the shell provisioner will + `vagrant reload --provision-with shell`, only the shell provisioner will be run. diff --git a/website/source/docs/cli/snapshot.html.md b/website/source/docs/cli/snapshot.html.md index 432336dea..7c83c3138 100644 --- a/website/source/docs/cli/snapshot.html.md +++ b/website/source/docs/cli/snapshot.html.md @@ -58,7 +58,7 @@ the pushed state. # Snapshot Save -**Command: `vagrant snapshot save NAME`** +**Command: `vagrant snapshot save [vm-name] NAME`** This command saves a new named snapshot. If this command is used, the `push` and `pop` subcommands cannot be safely used. diff --git a/website/source/docs/cli/up.html.md b/website/source/docs/cli/up.html.md index d654ab6f9..4342cda40 100644 --- a/website/source/docs/cli/up.html.md +++ b/website/source/docs/cli/up.html.md @@ -35,7 +35,7 @@ on a day-to-day basis. * `--provider x` - Bring the machine up with the given [provider](/docs/providers/). By default this is "virtualbox". -* `--provision` - Force the provisioners to run. +* `--[no-]provision` - Force, or prevent, the provisioners to run. * `--provision-with x,y,z` - This will only run the given provisioners. For example, if you have a `:shell` and `:chef_solo` provisioner and run diff --git a/website/source/docs/installation/source.html.md b/website/source/docs/installation/source.html.md index 9065e6498..9c1fd76c9 100644 --- a/website/source/docs/installation/source.html.md +++ b/website/source/docs/installation/source.html.md @@ -15,23 +15,15 @@ when using the official installer is not an option. This page details the steps and prerequisites for installing Vagrant from source. ## Install Ruby -You must have a modern Ruby (>= 2.0) in order to develop and build Vagrant. The +You must have a modern Ruby (>= 2.2) in order to develop and build Vagrant. The specific Ruby version is documented in the Vagrant's `gemspec`. Please refer to the `vagrant.gemspec` in the repository on GitHub, as it will contain the most -up-to-date requirement. This guide will not discuss how to install and manage Ruby. However, beware of the following pitfalls: +up-to-date requirement. This guide will not discuss how to install and manage Ruby. +However, beware of the following pitfalls: - Do **NOT** use the system Ruby - use a Ruby version manager like rvm or chruby -- Ensure you have the latest version of Rubygems -- Ensure you have installed a version of [Bundler](https://bundler.io) that is - compatible with Vagrant. - - The bundler constraint is a floating requirement in Vagrant. You will need to inspect the `vagrant.gemspec` to determine the version when you are compiling from source. For example, if the gemspec specifies version 1.2.3, you will need to install a version of Bundler that satisfies that constraint. - - You can install a specific version of bundler with the following command: - - ```shell - gem install bundler -v '1.2.3' - ``` +- Vagrant plugins are configured based on current environment. If plugins are installed + using Vagrant from source, they will not work from the package based Vagrant installation. ## Clone Vagrant Clone Vagrant's repository from GitHub into the directory where you keep code on your machine: @@ -50,7 +42,7 @@ $ cd /path/to/your/vagrant/clone Run the `bundle` command with a required version* to install the requirements: ```shell -$ bundle _1.10.6_ install +$ bundle install ``` You can now run Vagrant by running `bundle exec vagrant` from inside that diff --git a/website/source/docs/multi-machine/index.html.md b/website/source/docs/multi-machine/index.html.md index 4c3497a91..653af2438 100644 --- a/website/source/docs/multi-machine/index.html.md +++ b/website/source/docs/multi-machine/index.html.md @@ -85,6 +85,9 @@ The provisioners in this case will output "A", then "C", then "B". Notice that "B" is last. That is because the ordering is outside-in, in the order of the file. +If you want to apply a slightly different configuration to multiple machines, +see [this tip](/docs/vagrantfile/tips.html#loop-over-vm-definitions). + ## Controlling Multiple Machines The moment more than one machine is defined within a Vagrantfile, the diff --git a/website/source/docs/plugins/development-basics.html.md b/website/source/docs/plugins/development-basics.html.md index 84e6313ef..bb47f4b27 100644 --- a/website/source/docs/plugins/development-basics.html.md +++ b/website/source/docs/plugins/development-basics.html.md @@ -65,12 +65,6 @@ load any gems listed in the "plugins" group. Note that this also allows you to add multiple plugins to Vagrant for development, if your plugin works with another plugin. -With this structure in place, your workflow should be like any other -Ruby project, with one exception. Because Vagrant uses the internal -APIs of Bundler, see [Installing Vagrant from Source](https://github.com/mitchellh/vagrant/wiki/Installing-Vagrant-from-Source) -for tips on using the correct version of Bundler to install -dependencies. - When you want to manually test your plugin, use `bundle exec vagrant` in order to run Vagrant with your plugin loaded (as we specified in the Gemfile). diff --git a/website/source/docs/provisioning/ansible.html.md b/website/source/docs/provisioning/ansible.html.md index 12eeef8ba..3dda49b31 100644 --- a/website/source/docs/provisioning/ansible.html.md +++ b/website/source/docs/provisioning/ansible.html.md @@ -51,7 +51,7 @@ end ## Options -This section lists the specific options for the Ansible (remote) provisioner. In addition to the options listed below, this provisioner supports the [common options for both Ansible provisioners](/docs/provisioning/ansible_common.html). +This section lists the _specific_ options for the Ansible (remote) provisioner. In addition to the options listed below, this provisioner supports the [**common options** for both Ansible provisioners](/docs/provisioning/ansible_common.html). - `ask_sudo_pass` (boolean) - require Ansible to [prompt for a sudo password](https://docs.ansible.com/intro_getting_started.html#remote-connection-information). diff --git a/website/source/docs/provisioning/ansible_common.html.md b/website/source/docs/provisioning/ansible_common.html.md index 23817b983..2ed3b4e72 100644 --- a/website/source/docs/provisioning/ansible_common.html.md +++ b/website/source/docs/provisioning/ansible_common.html.md @@ -17,6 +17,10 @@ These options get passed to the `ansible-playbook` command that ships with Ansib Some of these options are for advanced usage only and should not be used unless you understand their purpose. +- `config_file` (string) - The path to an [Ansible Configuration file](https://docs.ansible.com/intro_configuration.html). + + By default, this option is not set, and Ansible will [search for a possible configuration file in some default locations](/docs/provisioning/ansible_intro.html#ANSIBLE_CONFIG). + - `extra_vars` (string or hash) - Pass additional variables (with highest priority) to the playbook. This parameter can be a path to a JSON or YAML file, or a hash. @@ -34,20 +38,27 @@ Some of these options are for advanced usage only and should not be used unless ``` These variables take the highest precedence over any other variables. -- `host_vars` (hash) - Set of inventory host variables to be included in the [auto-generated inventory file](https://docs.ansible.com/ansible/intro_inventory.html#host-variables). +- `galaxy_command` (template string) - The command pattern used to install Galaxy roles when `galaxy_role_file` is set. - Example: + The following (optional) placeholders can be used in this command pattern: + - `%{role_file}` is replaced by the absolute path to the `galaxy_role_file` option + - `%{roles_path}` is + - replaced by the absolute path to the `galaxy_roles_path` option when such option is defined, or + - replaced by the absolute path to a `roles` subdirectory sitting in the `playbook` parent directory. - ```ruby - ansible.host_vars = { - "host1" => {"http_port" => 80, - "maxRequestsPerChild" => 808}, - "host2" => {"http_port" => 303, - "maxRequestsPerChild" => 909} - } - ``` + By default, this option is set to - Note: This option has no effect when the `inventory_path` option is defined. + `ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force` + +- `galaxy_role_file` (string) - The path to the Ansible Galaxy role file. + + By default, this option is set to `nil` and Galaxy support is then disabled. + + Note: if an absolute path is given, the `ansible_local` provisioner will assume that it corresponds to the exact location on the guest system. + +- `galaxy_roles_path` (string) - The path to the directory where Ansible Galaxy roles must be installed + + By default, this option is set to `nil`, which means that the Galaxy roles will be installed in a `roles` subdirectory located in the parent directory of the `playbook` file. - `groups` (hash) - Set of inventory groups to be included in the [auto-generated inventory file](/docs/provisioning/ansible_intro.html). @@ -74,43 +85,44 @@ Some of these options are for advanced usage only and should not be used unless - Alphanumeric patterns are not supported (e.g. `db-[a:f]`, `vm[01:10]`). - This option has no effect when the `inventory_path` option is defined. +- `host_vars` (hash) - Set of inventory host variables to be included in the [auto-generated inventory file](https://docs.ansible.com/ansible/intro_inventory.html#host-variables). + + Example: + + ```ruby + ansible.host_vars = { + "host1" => {"http_port" => 80, + "maxRequestsPerChild" => 808}, + "host2" => {"http_port" => 303, + "maxRequestsPerChild" => 909} + } + ``` + + Note: This option has no effect when the `inventory_path` option is defined. + - `inventory_path` (string) - The path to an Ansible inventory resource (e.g. a [static inventory file](https://docs.ansible.com/intro_inventory.html), a [dynamic inventory script](https://docs.ansible.com/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](https://docs.ansible.com/intro_dynamic_inventory.html#using-multiple-inventory-sources)). By default, this option is disabled and Vagrant generates an inventory based on the `Vagrantfile` information. -- `galaxy_command` (template string) - The command pattern used to install Galaxy roles when `galaxy_role_file` is set. - - The following (optional) placeholders can be used in this command pattern: - - `%{role_file}` is replaced by the absolute path to the `galaxy_role_file` option - - `%{roles_path}` is - - replaced by the absolute path to the `galaxy_roles_path` option when such option is defined, or - - replaced by the absolute path to a `roles` subdirectory sitting in the `playbook` parent directory. - - By default, this option is set to - - `ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force` - -- `galaxy_role_file` (string) - The path to the Ansible Galaxy role file. - - By default, this option is set to `nil` and Galaxy support is then disabled. - - Note: if an absolute path is given, the `ansible_local` provisioner will assume that it corresponds to the exact location on the guest system. - -- `galaxy_roles_path` (string) - The path to the directory where Ansible Galaxy roles must be installed - - By default, this option is set to `nil`, which means that the Galaxy roles will be installed in a `roles` subdirectory located in the parent directory of the `playbook` file. - - `limit` (string or array of strings) - Set of machines or groups from the inventory file to further control which hosts [are affected](https://docs.ansible.com/glossary.html#limit-groups). The default value is set to the machine name (taken from `Vagrantfile`) to ensure that `vagrant provision` command only affect the expected machine. Setting `limit = "all"` can be used to make Ansible connect to all machines from the inventory file. +- `playbook_command` (string) - The command used to run playbooks. + + The default value is `ansible-playbook` + - `raw_arguments` (array of strings) - a list of additional `ansible-playbook` arguments. It is an *unsafe wildcard* that can be used to apply Ansible options that are not (yet) supported by this Vagrant provisioner. As of Vagrant 1.7, `raw_arguments` has the highest priority and its values can potentially override or break other Vagrant settings. - Example: `['--check', '-M /my/modules']`). + Examples: + - `['--check', '-M', '/my/modules']` + - `["--connection=paramiko", "--forks=10"]` + + **Caveat:** The `ansible` provisioner does not support whitespace characters in `raw_arguments` elements. Therefore **don't write** something like `["-c paramiko"]`, which will result with an invalid `" parmiko"` parameter value. - `skip_tags` (string or array of strings) - Only plays, roles and tasks that [*do not match* these values will be executed](https://docs.ansible.com/playbooks_tags.html). @@ -124,6 +136,8 @@ Some of these options are for advanced usage only and should not be used unless - `tags` (string or array of strings) - Only plays, roles and tasks [tagged with these values will be executed](https://docs.ansible.com/playbooks_tags.html) . +- `vault_password_file` (string) - The path of a file containing the password used by [Ansible Vault](https://docs.ansible.com/playbooks_vault.html#vault). + - `verbose` (boolean or string) - Set Ansible's verbosity to obtain detailed logging Default value is `false` (minimal verbosity). @@ -131,5 +145,3 @@ Some of these options are for advanced usage only and should not be used unless Examples: `true` (equivalent to `v`), `-vvv` (equivalent to `vvv`), `vvvv`. Note that when the `verbose` option is enabled, the `ansible-playbook` command used by Vagrant will be displayed. - -- `vault_password_file` (string) - The path of a file containing the password used by [Ansible Vault](https://docs.ansible.com/playbooks_vault.html#vault). diff --git a/website/source/docs/provisioning/ansible_intro.html.md b/website/source/docs/provisioning/ansible_intro.html.md index 0210e0e34..be3ef1ec0 100644 --- a/website/source/docs/provisioning/ansible_intro.html.md +++ b/website/source/docs/provisioning/ansible_intro.html.md @@ -11,9 +11,8 @@ description: |- The information below is applicable to both Vagrant Ansible provisioners: - - [`ansible`](/docs/provisioning/ansible.html), where Ansible is executed on the **Vagrant host** - - - [`ansible_local`](/docs/provisioning/ansible_local.html), where Ansible is executed on the **Vagrant guest** + - [`ansible`](/docs/provisioning/ansible.html), where Ansible is executed on the **Vagrant host** + - [`ansible_local`](/docs/provisioning/ansible_local.html), where Ansible is executed on the **Vagrant guest** The list of common options for these two provisioners is documented in a [separate documentation page](/docs/provisioning/ansible_common.html). @@ -113,7 +112,7 @@ Note that the generated inventory file is uploaded to the guest VM in a subdirec **Host variables:** -As of Vagrant 1.8.0, the [`host_vars`](/docs/provisioning/ansible_common.html) option can be used to set [variables for individual hosts](https://docs.ansible.com/ansible/intro_inventory.html#host-variables) in the generated inventory file (see also the notes on group variables below). +As of Vagrant 1.8.0, the [`host_vars`](/docs/provisioning/ansible_common.html#host_vars) option can be used to set [variables for individual hosts](https://docs.ansible.com/ansible/intro_inventory.html#host-variables) in the generated inventory file (see also the notes on group variables below). ``` Vagrant.configure("2") do |config| @@ -142,7 +141,7 @@ host2 ansible_ssh_host=... http_port=303 maxRequestsPerChild=909 **How to generate Inventory Groups:** -The [`groups`](/docs/provisioning/ansible_common.html) option can be used to pass a hash of group names and group members to be included in the generated inventory file. +The [`groups`](/docs/provisioning/ansible_common.html#groups) option can be used to pass a hash of group names and group members to be included in the generated inventory file. As of Vagrant 1.8.0, it is also possible to specify [group variables](https://docs.ansible.com/ansible/intro_inventory.html#group-variables), and group members as [host ranges (with numeric or alphabetic patterns)](https://docs.ansible.com/ansible/intro_inventory.html#hosts-and-groups). @@ -221,7 +220,7 @@ variable2=example The second option is for situations where you would like to have more control over the inventory management. -With the `inventory_path` option, you can reference a specific inventory resource (e.g. a static inventory file, a [dynamic inventory script](https://docs.ansible.com/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](https://docs.ansible.com/intro_dynamic_inventory.html#using-multiple-inventory-sources)). Vagrant will then use this inventory information instead of generating it. +With the [`inventory_path`](/docs/provisioning/ansible_common.html#inventory_path) option, you can reference a specific inventory resource (e.g. a static inventory file, a [dynamic inventory script](https://docs.ansible.com/intro_dynamic_inventory.html) or even [multiple inventories stored in the same directory](https://docs.ansible.com/intro_dynamic_inventory.html#using-multiple-inventory-sources)). Vagrant will then use this inventory information instead of generating it. A very simple inventory file for use with Vagrant might look like: @@ -237,9 +236,9 @@ config.vm.network :private_network, ip: "192.168.111.222" **Notes:** - - The machine names in `Vagrantfile` and `ansible.inventory_path` files should correspond, unless you use `ansible.limit` option to reference the correct machines. - - The SSH host addresses (and ports) must obviously be specified twice, in `Vagrantfile` and `ansible.inventory_path` files. - - Sharing hostnames across Vagrant host and guests might be a good idea (e.g. with some Ansible configuration task, or with a plugin like [`vagrant-hostmanager`](https://github.com/smdahlen/vagrant-hostmanager)). + - The machine names in `Vagrantfile` and `ansible.inventory_path` files should correspond, unless you use `ansible.limit` option to reference the correct machines. + - The SSH host addresses (and ports) must obviously be specified twice, in `Vagrantfile` and `ansible.inventory_path` files. + - Sharing hostnames across Vagrant host and guests might be a good idea (e.g. with some Ansible configuration task, or with a plugin like [`vagrant-hostmanager`](https://github.com/smdahlen/vagrant-hostmanager)). ### The Ansible Configuration File @@ -247,12 +246,11 @@ Certain settings in Ansible are (only) adjustable via a [configuration file](htt When shipping an Ansible configuration file it is good to know that: - - it is possible to reference an Ansible configuration file via `ANSIBLE_CONFIG` environment variable, if you want to be flexible about the location of this file. - - as of Ansible 1.5, the lookup order is the following: - - - `ANSIBLE_CONFIG` an environment variable - - `ansible.cfg` in the runtime working directory - - `.ansible.cfg` in the user home directory - - `/etc/ansible/ansible.cfg` - - - `ansible-playbook` doesn't look for a configuration file relative to the playbook file location (e.g. in the same directory) + - as of Ansible 1.5, the lookup order is the following: + - any path set as `ANSIBLE_CONFIG` environment variable + - `ansible.cfg` in the runtime working directory + - `.ansible.cfg` in the user home directory + - `/etc/ansible/ansible.cfg` + - Ansible commands don't look for a configuration file relative to the playbook file location (e.g. in the same directory) + - an `ansible.cfg` file located in the same directory as your `Vagrantfile` will be used by default. + - it is also possible to reference any other location with the [config_file](/docs/provisioning/ansible_common.html#config_file) provisioner option. In this case, Vagrant will set the `ANSIBLE_CONFIG` environment variable accordingly. diff --git a/website/source/docs/provisioning/ansible_local.html.md b/website/source/docs/provisioning/ansible_local.html.md index 44e8cc7a6..0efdcb3d8 100644 --- a/website/source/docs/provisioning/ansible_local.html.md +++ b/website/source/docs/provisioning/ansible_local.html.md @@ -55,7 +55,7 @@ end ## Options -This section lists the specific options for the Ansible Local provisioner. In addition to the options listed below, this provisioner supports the [common options for both Ansible provisioners](/docs/provisioning/ansible_common.html). +This section lists the _specific_ options for the Ansible Local provisioner. In addition to the options listed below, this provisioner supports the [**common options** for both Ansible provisioners](/docs/provisioning/ansible_common.html). - `install` (boolean) - Try to automatically install Ansible on the guest system. diff --git a/website/source/docs/provisioning/basic_usage.html.md b/website/source/docs/provisioning/basic_usage.html.md index 1939fe4ba..12bc91ef9 100644 --- a/website/source/docs/provisioning/basic_usage.html.md +++ b/website/source/docs/provisioning/basic_usage.html.md @@ -48,7 +48,8 @@ end The benefit of the block-based syntax is that with more than a couple options it can greatly improve readability. Additionally, some provisioners, like the Chef provisioner, have special methods that can be called within that -block to ease configuration that cannot be done with the key/value approach. +block to ease configuration that cannot be done with the key/value approach, +or you can use this syntax to pass arguments to a shell script. The attributes that can be set in a single-line are the attributes that are set with the `=` style, such as `inline = "echo hello"` above. If the @@ -107,6 +108,11 @@ Vagrant.configure("2") do |config| end ``` +You can also set `run:` to `"never"` if you have an optional provisioner +that you want to mention to the user in a "post up message" or that +requires some other configuration before it is possible, then call this +with `vagrant provision --provision-with bootstrap`. + If you are using the block format, you must specify it outside of the block, as shown below: @@ -150,7 +156,7 @@ The ordering of the provisioners will be to echo "foo", "baz", then ordering is _outside in_. With multiple provisioners, use the `--provision-with` setting along -with names to get more fine grainted control over what is run and when. +with names to get more fine grained control over what is run and when. ## Overriding Provisioner Settings diff --git a/website/source/docs/provisioning/puppet_apply.html.md b/website/source/docs/provisioning/puppet_apply.html.md index 2de1c1542..cf1a69461 100644 --- a/website/source/docs/provisioning/puppet_apply.html.md +++ b/website/source/docs/provisioning/puppet_apply.html.md @@ -52,6 +52,9 @@ available below this section. * `environment_path` (string) - Path to the directory that contains environment files on the host disk. +* `environment_variables` (hash) - A hash of string key/value pairs to be set as + environment variables before the puppet apply run. + * `options` (array of strings) - Additionally options to pass to the Puppet executable when running Puppet. diff --git a/website/source/docs/provisioning/salt.html.md b/website/source/docs/provisioning/salt.html.md index 63d1932b5..36416936c 100644 --- a/website/source/docs/provisioning/salt.html.md +++ b/website/source/docs/provisioning/salt.html.md @@ -62,7 +62,7 @@ on this machine. Not supported on Windows guest machines. * `install_type` (stable | git | daily | testing) - Whether to install from a distribution's stable package manager, git tree-ish, daily ppa, or testing repository. -* `install_args` (develop) - When performing a git install, you can specify a branch, tag, or any treeish. Not supported on Windows. +* `install_args` (string, default: "develop") - When performing a git install, you can specify a branch, tag, or any treeish. Not supported on Windows. * `always_install` (boolean) - Installs salt binaries even if they are already detected, default `false` @@ -80,11 +80,11 @@ These only make sense when `no_minion` is `false`. * `minion_config` (string, default: "salt/minion") - Path to a custom salt minion config file. -* `minion_key` (string) - Path to your minion key +* `minion_key` (string, default: "salt/key/minion.key") - Path to your minion key * `minion_id` (string) - Unique identifier for minion. Used for masterless and preseeding keys. -* `minion_pub` (salt/key/minion.pub) - Path to your minion +* `minion_pub` (string, default: "salt/key/minion.pub") - Path to your minion public key * `grains_config` (string) - Path to a custom salt grains file. On Windows, the minion needs `ipc_mode: tcp` set otherwise it will [fail to communicate](https://github.com/saltstack/salt/issues/22796) with the master. @@ -97,9 +97,9 @@ These only make sense when `install_master` is `true`. Not supported on Windows * `master_config` (string, default: "salt/master") Path to a custom salt master config file. -* `master_key` (salt/key/master.pem) - Path to your master key. +* `master_key` (string, default: "salt/key/master.pem") - Path to your master key. -* `master_pub` (salt/key/master.pub) - Path to your master public key. +* `master_pub` (string, default: "salt/key/master.pub") - Path to your master public key. * `seed_master` (dictionary) - Upload keys to master, thereby pre-seeding it before use. Example: `{minion_name:/path/to/key.pub}` diff --git a/website/source/docs/provisioning/shell.html.md b/website/source/docs/provisioning/shell.html.md index 8a272f4e5..3530c181d 100644 --- a/website/source/docs/provisioning/shell.html.md +++ b/website/source/docs/provisioning/shell.html.md @@ -79,6 +79,10 @@ The remainder of the available options are optional: enable auto-login for Windows as the user must be logged in for interactive mode to work. +* `md5` (string) - MD5 checksum used to validate remotely downloaded shell files. + +* `sha1` (string) - SHA1 checksum used to validate remotely downloaded shell files. + ## Inline Scripts diff --git a/website/source/docs/synced-folders/nfs.html.md b/website/source/docs/synced-folders/nfs.html.md index a6a156773..bbd853feb 100644 --- a/website/source/docs/synced-folders/nfs.html.md +++ b/website/source/docs/synced-folders/nfs.html.md @@ -181,6 +181,10 @@ Cmnd_Alias VAGRANT_EXPORTS_REMOVE = /bin/sed -r -e * d -ibak /tmp/exports %vagrant ALL=(root) NOPASSWD: VAGRANT_EXPORTS_ADD, VAGRANT_NFSD_CHECK, VAGRANT_NFSD_START, VAGRANT_NFSD_APPLY, VAGRANT_EXPORTS_REMOVE ``` +If you don't want to edit `/etc/sudoers` directly, you can create +`/etc/sudoers.d/vagrant-syncedfolders` with the appropriate entries, +assuming `/etc/sudoers.d` has been enabled. + ## Other Notes **Encrypted folders:** If you have an encrypted disk, then NFS very often diff --git a/website/source/docs/vagrantfile/ssh_settings.html.md b/website/source/docs/vagrantfile/ssh_settings.html.md index c45b79a6c..29d1bc8bf 100644 --- a/website/source/docs/vagrantfile/ssh_settings.html.md +++ b/website/source/docs/vagrantfile/ssh_settings.html.md @@ -126,6 +126,17 @@ only affects the shell to use when executing commands internally in Vagrant.
+`config.ssh.export_command_template` - The template used to generate +exported environment variables in the active session. This can be useful +when using a Bourne incompatible shell like C shell. The template supports +two variables which are replaced with the desired environment variable key and +environment variable value: `%ENV_KEY%` and `%ENV_VALUE%`. The default template +is: + +```ruby +config.ssh.export_command_template = 'export %ENV_KEY%="%ENV_VALUE%"' +``` + `config.ssh.sudo_command` - The command to use when executing a command with `sudo`. This defaults to `sudo -E -H %c`. The `%c` will be replaced by the command that is being executed. diff --git a/website/source/docs/virtualbox/common-issues.html.md b/website/source/docs/virtualbox/common-issues.html.md index 6b0778943..30af68cb2 100644 --- a/website/source/docs/virtualbox/common-issues.html.md +++ b/website/source/docs/virtualbox/common-issues.html.md @@ -16,7 +16,7 @@ as well as solutions for those issues. If Vagrant commands are hanging on Windows because they're communicating to VirtualBox, this may be caused by a permissions issue with VirtualBox. -This is easy to fix. Start VirtualBox as a normal user or as an +This is easy to fix. Starting VirtualBox as a normal user or as an administrator will prevent you from using it in the opposite way. Please keep in mind that when Vagrant interacts with VirtualBox, it will interact with it with the same access level as the console running Vagrant. diff --git a/website/source/docs/virtualbox/configuration.html.md b/website/source/docs/virtualbox/configuration.html.md index fe6040d59..bd1845bb1 100644 --- a/website/source/docs/virtualbox/configuration.html.md +++ b/website/source/docs/virtualbox/configuration.html.md @@ -67,7 +67,7 @@ config.vm.provider 'virtualbox' do |v| end ``` -If you do not want backward compatbility and want to force users to +If you do not want backward compatibility and want to force users to support linked cloning, you can use `Vagrant.require_version` with 1.8.
diff --git a/website/source/downloads.html.erb b/website/source/downloads.html.erb index dd3c967dc..07c37010e 100644 --- a/website/source/downloads.html.erb +++ b/website/source/downloads.html.erb @@ -22,8 +22,8 @@ description: |- online and you can - verify the checksums signature file - + verify the checksum's signature file + , which has been signed using HashiCorp's GPG key. You can also download older versions of Vagrant from the releases service.

diff --git a/website/source/layouts/_footer.erb b/website/source/layouts/_footer.erb index 29da13f4c..fecd485bc 100644 --- a/website/source/layouts/_footer.erb +++ b/website/source/layouts/_footer.erb @@ -12,6 +12,7 @@
  • VMware
  • Boxes
  • Support
  • +
  • Security
  • Book
  • diff --git a/website/source/layouts/_header.erb b/website/source/layouts/_header.erb index 84d5e9a27..42a3ddc94 100644 --- a/website/source/layouts/_header.erb +++ b/website/source/layouts/_header.erb @@ -5,7 +5,7 @@ <%= title_for(current_page) %> - + <%= stylesheet_link_tag "application" %> @@ -32,7 +32,7 @@ <% end %> diff --git a/website/source/security.html.erb b/website/source/security.html.erb new file mode 100644 index 000000000..82b39cd14 --- /dev/null +++ b/website/source/security.html.erb @@ -0,0 +1,33 @@ +--- +layout: "about" +sidebar_current: "about-security" +page_title: "Security" +description: |- + Vagrant takes security very seriously. Please responsibly disclose any security vulnerabilities found and we'll handle it quickly. +--- + +

    Vagrant Security

    + +

    + We understand that many users place a high level of trust in HashiCorp + and the tools we build. We apply best practices and focus on security to + make sure we can maintain the trust of the community. +

    + +

    + We deeply appreciate any effort to disclose vulnerabilities responsibly. +

    + +

    + If you would like to report a vulnerability, please see the HashiCorp security + page, which has the proper email to communicate with as well as our + PGP key. Please do not create a GitHub issue for security + concerns. +

    + +

    + If you need to report a non-security related bug, please open and issue + on the Vagrant + GitHub repository. +