Merge pull request #5912 from mitchellh/sethvargo/subprocess_escape

Automatically restore the original environment if we are runnings something outside of the embedded installer
This commit is contained in:
Seth Vargo 2015-07-09 16:10:33 -06:00
commit 04ed8d3d03
4 changed files with 123 additions and 45 deletions

View File

@ -1,7 +1,17 @@
require "bundler"
module Vagrant module Vagrant
module Util module Util
class Env class Env
# def self.with_original_env
original_env = ENV.to_hash
ENV.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV)
ENV.update(Vagrant.original_env)
yield
ensure
ENV.replace(original_env.to_hash)
end
# Execute the given command, removing any Ruby-specific environment # Execute the given command, removing any Ruby-specific environment
# variables. This is an "enhanced" version of `Bundler.with_clean_env`, # variables. This is an "enhanced" version of `Bundler.with_clean_env`,
# which only removes Bundler-specific values. We need to remove all # which only removes Bundler-specific values. We need to remove all
@ -25,18 +35,16 @@ module Vagrant
# #
# @param [Proc] block # @param [Proc] block
# the block to execute with the cleaned environment # the block to execute with the cleaned environment
# def self.with_clean_env
def self.with_clean_env(&block) with_original_env do
original = ENV.to_hash ENV["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"]
ENV.delete_if { |k,_| k[0,7] == "BUNDLE_" }
ENV.delete('_ORIGINAL_GEM_PATH') if ENV.has_key? "RUBYOPT"
ENV.delete_if { |k,_| k.start_with?('BUNDLE_') } ENV["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "")
ENV.delete_if { |k,_| k.start_with?('GEM_') } ENV["RUBYOPT"] = ENV["RUBYOPT"].sub("-I#{File.expand_path('..', __FILE__)}", "")
ENV.delete_if { |k,_| k.start_with?('RUBY') } end
yield yield
ensure end
ENV.replace(original.to_hash)
end end
end end
end end

View File

@ -72,11 +72,14 @@ module Vagrant
process.io.stderr = stderr_writer process.io.stderr = stderr_writer
process.duplex = true process.duplex = true
# Special installer-related things
if Vagrant.in_installer?
installer_dir = ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"].to_s.downcase
# If we're in an installer on Mac and we're executing a command # If we're in an installer on Mac and we're executing a command
# in the installer context, then force DYLD_LIBRARY_PATH to look # in the installer context, then force DYLD_LIBRARY_PATH to look
# at our libs first. # at our libs first.
if Vagrant.in_installer? && Platform.darwin? if Platform.darwin?
installer_dir = ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"].to_s.downcase
if @command[0].downcase.include?(installer_dir) if @command[0].downcase.include?(installer_dir)
@logger.info("Command in the installer. Specifying DYLD_LIBRARY_PATH...") @logger.info("Command in the installer. Specifying DYLD_LIBRARY_PATH...")
process.environment["DYLD_LIBRARY_PATH"] = process.environment["DYLD_LIBRARY_PATH"] =
@ -91,13 +94,17 @@ module Vagrant
end end
end end
# Reset the Bundler environment back - this is required for anyone who # If the command that is being run is not inside the installer, reset
# is not using the official Vagrant installers and is running Vagrant # the original environment - this is required for shelling out to
# via bundler # other subprocesses that depend on environment variables (like Ruby
if defined?(Bundler::ORIGINAL_ENV) # and $GEM_PATH for example)
Bundler::ORIGINAL_ENV.each do |k, v| if !@command[0].downcase.include?(installer_dir)
process.environment[k] = v @logger.info("Command not in installer, restoring original environment...")
jailbreak(process.environment)
end end
else
@logger.info("Vagrant not running in installer, restoring original environment...")
jailbreak(process.environment)
end end
# Set the environment on the process if we must # Set the environment on the process if we must
@ -248,6 +255,58 @@ module Vagrant
@stderr = stderr @stderr = stderr
end end
end end
private
# This is, quite possibly, the saddest function in all of Vagrant.
#
# If a user is running Vagrant via Bundler (but not via the official
# installer), we want to reset to the "original" environment so that when
# shelling out to other Ruby processes (specifically), the original
# environment is restored. This is super important for things like
# rbenv and chruby, who rely on environment variables to locate gems, but
# Bundler stomps on those environment variables like an angry T-Rex after
# watching Jurassic Park 2 and realizing they replaced you with CGI.
#
# If a user is running in Vagrant via the official installer, BUT trying
# to execute a subprocess *outside* of the installer, we want to reset to
# the "original" environment. In this case, the Vagrant installer actually
# knows what the original environment was and replaces it completely.
#
# Finally, we reset any Bundler-specific environment variables, since the
# subprocess being called could, itself, be Bundler. And Bundler does not
# behave very nicely in these circumstances.
#
# This function was added in Vagrant 1.7.3, but there is a failsafe
# because the author doesn't trust himself that this functionality won't
# break existing assumptions, so users can specify
# `VAGRANT_SKIP_SUBPROCESS_JAILBREAK` and none of the above will happen.
#
# This function modifies the given hash in place!
#
# @return [nil]
def jailbreak(env = {})
return if ENV.key?("VAGRANT_SKIP_SUBPROCESS_JAILBREAK")
env.replace(::Bundler::ORIGINAL_ENV) if defined?(::Bundler::ORIGINAL_ENV)
env.merge!(Vagrant.original_env)
# Bundler does this, so I guess we should as well, since I think it
# other subprocesses that use Bundler will reload it
env["MANPATH"] = ENV["BUNDLE_ORIG_MANPATH"]
# Replace all current environment BUNDLE_ variables to nil
ENV.each do |k,_|
env[k] = nil if k[0,7] == "BUNDLE_"
end
# If RUBYOPT was set, unset it with Bundler
if ENV.key?("RUBYOPT")
env["RUBYOPT"] = ENV["RUBYOPT"].sub("-rbundler/setup", "")
end
nil
end
end end
end end
end end

View File

@ -114,7 +114,6 @@ module VagrantPlugins
# Knife is not part of the current Vagrant bundle, so it needs to run # Knife is not part of the current Vagrant bundle, so it needs to run
# in the context of the system. # in the context of the system.
Vagrant.global_lock do Vagrant.global_lock do
Vagrant::Util::Env.with_clean_env do
command = ["knife", deletable, "delete", "--yes", node_name] command = ["knife", deletable, "delete", "--yes", node_name]
r = Vagrant::Util::Subprocess.execute(*command) r = Vagrant::Util::Subprocess.execute(*command)
if r.exit_code != 0 if r.exit_code != 0
@ -129,5 +128,4 @@ module VagrantPlugins
end end
end end
end end
end
end end

View File

@ -90,6 +90,19 @@ Note that any `vagrant plugin` commands automatically don't load any
plugins, so if you do install any unstable plugins, you can always use plugins, so if you do install any unstable plugins, you can always use
the `vagrant plugin` commands without having to worry. the `vagrant plugin` commands without having to worry.
## VAGRANT\_SKIP\_SUBPROCESS\_JAILBREAK
As of Vagrant 1.7.3, Vagrant tries to intelligently detect if it is running in
the installer or running via Bundler. Although not officially supported, Vagrant
tries its best to work when executed via Bundler. When Vagrant detects that you
have spawned a subprocess that lives outside of Vagrant's installer, Vagrant
will do its best to reset the preserved environment dring the subprocess
execution.
If Vagrant detects it is running outside of the officially installer, the
original environment will always be restored. You can disable this automatic
jailbreak by setting the `VAGRANT_SKIP_SUBPROCES_JAILBREAK`.
## VAGRANT\_VAGRANTFILE ## VAGRANT\_VAGRANTFILE
This specifies the filename of the Vagrantfile that Vagrant searches for. This specifies the filename of the Vagrantfile that Vagrant searches for.