2012-04-19 04:53:19 +00:00
|
|
|
require "pathname"
|
|
|
|
require "tempfile"
|
2012-01-07 01:36:51 +00:00
|
|
|
|
2013-07-12 13:40:41 +00:00
|
|
|
require "vagrant/util/downloader"
|
2014-10-24 05:48:48 +00:00
|
|
|
require "vagrant/util/retryable"
|
2013-07-12 13:40:41 +00:00
|
|
|
|
2012-04-19 04:53:19 +00:00
|
|
|
module VagrantPlugins
|
|
|
|
module Shell
|
2012-11-07 05:21:36 +00:00
|
|
|
class Provisioner < Vagrant.plugin("2", :provisioner)
|
2014-10-24 05:49:26 +00:00
|
|
|
include Vagrant::Util::Retryable
|
2014-10-24 05:48:48 +00:00
|
|
|
|
2013-01-13 23:48:52 +00:00
|
|
|
def provision
|
2014-04-12 04:38:26 +00:00
|
|
|
args = ""
|
|
|
|
if config.args.is_a?(String)
|
|
|
|
args = " #{config.args.to_s}"
|
|
|
|
elsif config.args.is_a?(Array)
|
|
|
|
args = config.args.map { |a| quote_and_escape(a) }
|
|
|
|
args = " #{args.join(" ")}"
|
|
|
|
end
|
|
|
|
|
2017-04-15 14:12:58 +00:00
|
|
|
case @machine.config.vm.communicator
|
|
|
|
when :winrm
|
2014-04-12 04:38:26 +00:00
|
|
|
provision_winrm(args)
|
2017-04-15 14:12:58 +00:00
|
|
|
when :winssh
|
|
|
|
provision_winssh(args)
|
2014-04-12 04:27:54 +00:00
|
|
|
else
|
2014-04-12 04:38:26 +00:00
|
|
|
provision_ssh(args)
|
2014-04-12 04:27:54 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
|
2014-04-12 04:40:05 +00:00
|
|
|
# This handles outputting the communication data back to the UI
|
|
|
|
def handle_comm(type, data)
|
|
|
|
if [:stderr, :stdout].include?(type)
|
|
|
|
# Output the data with the proper color based on the stream.
|
|
|
|
color = type == :stdout ? :green : :red
|
|
|
|
|
2014-04-19 06:48:05 +00:00
|
|
|
# Clear out the newline since we add one
|
|
|
|
data = data.chomp
|
|
|
|
return if data.empty?
|
|
|
|
|
|
|
|
options = {}
|
2014-04-12 04:40:05 +00:00
|
|
|
options[:color] = color if !config.keep_color
|
|
|
|
|
2014-04-19 06:48:05 +00:00
|
|
|
@machine.ui.info(data.chomp, options)
|
2014-04-12 04:40:05 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-04-12 04:27:54 +00:00
|
|
|
# This is the provision method called if SSH is what is running
|
|
|
|
# on the remote end, which assumes a POSIX-style host.
|
2014-04-12 04:38:26 +00:00
|
|
|
def provision_ssh(args)
|
2015-12-22 19:23:56 +00:00
|
|
|
env = config.env.map { |k,v| "#{k}=#{quote_and_escape(v.to_s)}" }
|
|
|
|
env = env.join(" ")
|
2015-11-24 00:24:06 +00:00
|
|
|
|
|
|
|
command = "chmod +x '#{config.upload_path}'"
|
|
|
|
command << " &&"
|
|
|
|
command << " #{env}" if !env.empty?
|
|
|
|
command << " #{config.upload_path}#{args}"
|
2011-01-23 19:52:24 +00:00
|
|
|
|
2013-01-13 23:48:52 +00:00
|
|
|
with_script_file do |path|
|
|
|
|
# Upload the script to the machine
|
|
|
|
@machine.communicate.tap do |comm|
|
2013-04-11 12:05:50 +00:00
|
|
|
# Reset upload path permissions for the current ssh user
|
2014-10-24 05:48:48 +00:00
|
|
|
info = nil
|
|
|
|
retryable(on: Vagrant::Errors::SSHNotReady, tries: 3, sleep: 2) do
|
|
|
|
info = @machine.ssh_info
|
|
|
|
raise Vagrant::Errors::SSHNotReady if info.nil?
|
|
|
|
end
|
|
|
|
|
|
|
|
user = info[:username]
|
2013-04-11 12:05:50 +00:00
|
|
|
comm.sudo("chown -R #{user} #{config.upload_path}",
|
2014-05-22 16:35:12 +00:00
|
|
|
error_check: false)
|
2013-04-11 12:05:50 +00:00
|
|
|
|
2013-01-13 23:48:52 +00:00
|
|
|
comm.upload(path.to_s, config.upload_path)
|
2011-01-23 19:52:24 +00:00
|
|
|
|
2015-05-31 03:57:49 +00:00
|
|
|
if config.name
|
2015-04-18 04:35:21 +00:00
|
|
|
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
2015-05-31 03:57:49 +00:00
|
|
|
script: "script: #{config.name}"))
|
2015-04-18 04:35:21 +00:00
|
|
|
elsif config.path
|
2014-03-08 22:43:35 +00:00
|
|
|
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
2013-04-10 18:27:45 +00:00
|
|
|
script: path.to_s))
|
|
|
|
else
|
2014-03-08 22:43:35 +00:00
|
|
|
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
2013-04-10 18:27:45 +00:00
|
|
|
script: "inline script"))
|
|
|
|
end
|
|
|
|
|
2013-01-13 23:48:52 +00:00
|
|
|
# Execute it with sudo
|
2014-06-23 09:22:12 +00:00
|
|
|
comm.execute(
|
|
|
|
command,
|
|
|
|
sudo: config.privileged,
|
|
|
|
error_key: :ssh_bad_exit_status_muted
|
|
|
|
) do |type, data|
|
2017-04-15 14:12:58 +00:00
|
|
|
handle_comm(type, data)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# This is the provision method called if Windows OpenSSH is what is running
|
|
|
|
# on the remote end, which assumes a non-POSIX-style host.
|
|
|
|
def provision_winssh(args)
|
|
|
|
with_script_file do |path|
|
|
|
|
# Upload the script to the machine
|
|
|
|
@machine.communicate.tap do |comm|
|
|
|
|
env = config.env.map{|k,v| comm.generate_environment_export(k, v)}.join
|
|
|
|
remote_ext = @machine.config.winssh.shell == "powershell" ? "ps1" : "bat"
|
|
|
|
upload_path = "C:\\Windows\\Temp\\#{File.basename(path)}.#{remote_ext}"
|
|
|
|
if remote_ext == "ps1"
|
|
|
|
# Copy powershell_args from configuration
|
|
|
|
shell_args = config.powershell_args
|
|
|
|
# For PowerShell scripts bypass the execution policy unless already specified
|
|
|
|
shell_args += " -ExecutionPolicy Bypass" if config.powershell_args !~ /[-\/]ExecutionPolicy/i
|
|
|
|
# CLIXML output is kinda useless, especially on non-windows hosts
|
|
|
|
shell_args += " -OutputFormat Text" if config.powershell_args !~ /[-\/]OutputFormat/i
|
|
|
|
command = "#{env}\npowershell #{shell_args} #{upload_path}#{args}"
|
|
|
|
else
|
|
|
|
command = "#{env}\n#{upload_path}#{args}"
|
|
|
|
end
|
|
|
|
|
|
|
|
# Reset upload path permissions for the current ssh user
|
|
|
|
info = nil
|
|
|
|
retryable(on: Vagrant::Errors::SSHNotReady, tries: 3, sleep: 2) do
|
|
|
|
info = @machine.ssh_info
|
|
|
|
raise Vagrant::Errors::SSHNotReady if info.nil?
|
|
|
|
end
|
|
|
|
|
|
|
|
comm.upload(path.to_s, upload_path)
|
|
|
|
|
|
|
|
if config.name
|
|
|
|
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
|
|
|
script: "script: #{config.name}"))
|
|
|
|
elsif config.path
|
|
|
|
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
|
|
|
script: path.to_s))
|
|
|
|
else
|
|
|
|
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
|
|
|
script: "inline script"))
|
|
|
|
end
|
|
|
|
|
|
|
|
# Execute it with sudo
|
|
|
|
comm.execute(
|
|
|
|
command,
|
|
|
|
sudo: config.privileged,
|
|
|
|
error_key: :ssh_bad_exit_status_muted
|
|
|
|
) do |type, data|
|
2014-04-12 04:40:05 +00:00
|
|
|
handle_comm(type, data)
|
2011-12-15 03:02:10 +00:00
|
|
|
end
|
2011-01-23 19:52:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-04-12 04:27:54 +00:00
|
|
|
# This provisions using WinRM, which assumes a PowerShell
|
|
|
|
# console on the other side.
|
2014-04-12 04:38:26 +00:00
|
|
|
def provision_winrm(args)
|
2014-04-12 15:45:04 +00:00
|
|
|
if @machine.guest.capability?(:wait_for_reboot)
|
|
|
|
@machine.guest.capability(:wait_for_reboot)
|
|
|
|
end
|
2014-04-12 04:38:26 +00:00
|
|
|
|
|
|
|
with_script_file do |path|
|
|
|
|
@machine.communicate.tap do |comm|
|
|
|
|
# Make sure that the upload path has an extension, since
|
|
|
|
# having an extension is critical for Windows execution
|
|
|
|
upload_path = config.upload_path.to_s
|
|
|
|
if File.extname(upload_path) == ""
|
|
|
|
upload_path += File.extname(path.to_s)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Upload it
|
|
|
|
comm.upload(path.to_s, upload_path)
|
|
|
|
|
2015-11-24 00:24:06 +00:00
|
|
|
# Build the environment
|
2015-12-22 19:23:56 +00:00
|
|
|
env = config.env.map { |k,v| "$env:#{k} = #{quote_and_escape(v.to_s)}" }
|
|
|
|
env = env.join("; ")
|
2015-11-24 00:24:06 +00:00
|
|
|
|
2014-04-12 04:38:26 +00:00
|
|
|
# Calculate the path that we'll be executing
|
|
|
|
exec_path = upload_path
|
|
|
|
exec_path.gsub!('/', '\\')
|
|
|
|
exec_path = "c:#{exec_path}" if exec_path.start_with?("\\")
|
|
|
|
|
2014-10-23 16:53:14 +00:00
|
|
|
# Copy powershell_args from configuration
|
|
|
|
shell_args = config.powershell_args
|
|
|
|
|
2014-09-23 19:46:25 +00:00
|
|
|
# For PowerShell scripts bypass the execution policy unless already specified
|
2014-10-23 16:53:14 +00:00
|
|
|
shell_args += " -ExecutionPolicy Bypass" if config.powershell_args !~ /[-\/]ExecutionPolicy/i
|
|
|
|
|
|
|
|
# CLIXML output is kinda useless, especially on non-windows hosts
|
|
|
|
shell_args += " -OutputFormat Text" if config.powershell_args !~ /[-\/]OutputFormat/i
|
|
|
|
|
2014-05-01 02:35:02 +00:00
|
|
|
command = "#{exec_path}#{args}"
|
2014-09-23 19:46:25 +00:00
|
|
|
command = "powershell #{shell_args.to_s} -file #{command}" if
|
2014-05-01 02:35:02 +00:00
|
|
|
File.extname(exec_path).downcase == '.ps1'
|
2014-04-12 04:38:26 +00:00
|
|
|
|
2015-11-24 00:24:06 +00:00
|
|
|
# Append the environment
|
|
|
|
if !env.empty?
|
|
|
|
command = "#{env}; #{command}"
|
|
|
|
end
|
|
|
|
|
2015-05-31 03:57:49 +00:00
|
|
|
if config.name
|
2015-04-18 04:35:21 +00:00
|
|
|
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
2015-05-31 03:57:49 +00:00
|
|
|
script: "script: #{config.name}"))
|
2015-04-18 04:35:21 +00:00
|
|
|
elsif config.path
|
2014-09-23 19:46:25 +00:00
|
|
|
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.runningas",
|
|
|
|
local: config.path.to_s, remote: exec_path))
|
2014-04-12 04:42:34 +00:00
|
|
|
else
|
|
|
|
@machine.ui.detail(I18n.t("vagrant.provisioners.shell.running",
|
|
|
|
script: "inline PowerShell script"))
|
|
|
|
end
|
|
|
|
|
2014-04-12 04:38:26 +00:00
|
|
|
# Execute it with sudo
|
2015-11-18 20:51:18 +00:00
|
|
|
comm.sudo(command, { elevated: config.privileged, interactive: config.powershell_elevated_interactive }) do |type, data|
|
2014-04-12 04:40:05 +00:00
|
|
|
handle_comm(type, data)
|
2014-04-12 04:38:26 +00:00
|
|
|
end
|
2014-04-12 04:43:15 +00:00
|
|
|
end
|
2014-04-12 04:38:26 +00:00
|
|
|
end
|
2014-04-12 04:27:54 +00:00
|
|
|
end
|
2011-12-15 06:43:45 +00:00
|
|
|
|
2013-11-25 21:36:51 +00:00
|
|
|
# Quote and escape strings for shell execution, thanks to Capistrano.
|
2013-07-19 01:35:37 +00:00
|
|
|
def quote_and_escape(text, quote = '"')
|
|
|
|
"#{quote}#{text.gsub(/#{quote}/) { |m| "#{m}\\#{m}#{m}" }}#{quote}"
|
|
|
|
end
|
|
|
|
|
2011-07-06 06:09:36 +00:00
|
|
|
# This method yields the path to a script to upload and execute
|
|
|
|
# on the remote server. This method will properly clean up the
|
|
|
|
# script file if needed.
|
|
|
|
def with_script_file
|
2014-04-12 04:41:45 +00:00
|
|
|
ext = nil
|
2013-04-23 06:32:13 +00:00
|
|
|
script = nil
|
|
|
|
|
2013-07-12 13:40:41 +00:00
|
|
|
if config.remote?
|
2014-04-12 04:27:54 +00:00
|
|
|
download_path = @machine.env.tmp_path.join(
|
|
|
|
"#{@machine.id}-remote-script")
|
2013-07-12 13:40:41 +00:00
|
|
|
download_path.delete if download_path.file?
|
|
|
|
|
2014-04-12 04:32:28 +00:00
|
|
|
begin
|
2016-11-10 00:00:09 +00:00
|
|
|
Vagrant::Util::Downloader.new(
|
|
|
|
config.path,
|
|
|
|
download_path,
|
|
|
|
md5: config.md5,
|
|
|
|
sha1: config.sha1
|
|
|
|
).download!
|
2014-04-12 04:41:45 +00:00
|
|
|
ext = File.extname(config.path)
|
2014-04-12 04:32:28 +00:00
|
|
|
script = download_path.read
|
|
|
|
ensure
|
|
|
|
download_path.delete if download_path.file?
|
|
|
|
end
|
2013-07-12 13:40:41 +00:00
|
|
|
elsif config.path
|
2011-07-06 06:09:36 +00:00
|
|
|
# Just yield the path to that file...
|
2013-01-13 23:48:52 +00:00
|
|
|
root_path = @machine.env.root_path
|
2014-04-12 04:41:45 +00:00
|
|
|
ext = File.extname(config.path)
|
2013-04-23 06:32:13 +00:00
|
|
|
script = Pathname.new(config.path).expand_path(root_path).read
|
|
|
|
else
|
|
|
|
# The script is just the inline code...
|
2014-04-12 04:41:45 +00:00
|
|
|
ext = ".ps1"
|
2013-04-23 06:32:13 +00:00
|
|
|
script = config.inline
|
2011-07-06 06:09:36 +00:00
|
|
|
end
|
|
|
|
|
2013-09-19 11:33:58 +00:00
|
|
|
# Replace Windows line endings with Unix ones unless binary file
|
2014-04-12 04:32:28 +00:00
|
|
|
# or we're running on Windows.
|
2014-05-08 21:51:10 +00:00
|
|
|
if !config.binary && @machine.config.vm.communicator != :winrm
|
2015-09-12 13:32:05 +00:00
|
|
|
begin
|
|
|
|
script.gsub!(/\r\n?$/, "\n")
|
|
|
|
rescue ArgumentError
|
|
|
|
script = script.force_encoding("ASCII-8BIT").gsub(/\r\n?$/, "\n")
|
|
|
|
end
|
2014-04-12 04:32:28 +00:00
|
|
|
end
|
2013-04-23 06:32:13 +00:00
|
|
|
|
2011-07-06 06:09:36 +00:00
|
|
|
# Otherwise we have an inline script, we need to Tempfile it,
|
|
|
|
# and handle it specially...
|
2014-04-12 04:41:45 +00:00
|
|
|
file = Tempfile.new(['vagrant-shell', ext])
|
2012-10-13 02:51:25 +00:00
|
|
|
|
2012-10-12 00:14:59 +00:00
|
|
|
# Unless you set binmode, on a Windows host the shell script will
|
|
|
|
# have CRLF line endings instead of LF line endings, causing havoc
|
|
|
|
# when the guest executes it. This fixes [GH-1181].
|
|
|
|
file.binmode
|
2012-10-13 02:51:25 +00:00
|
|
|
|
2011-07-06 06:09:36 +00:00
|
|
|
begin
|
2013-04-23 06:32:13 +00:00
|
|
|
file.write(script)
|
2011-07-06 06:09:36 +00:00
|
|
|
file.fsync
|
2013-02-07 23:23:18 +00:00
|
|
|
file.close
|
2011-07-06 06:09:36 +00:00
|
|
|
yield file.path
|
|
|
|
ensure
|
|
|
|
file.close
|
|
|
|
file.unlink
|
|
|
|
end
|
|
|
|
end
|
2011-01-23 19:52:24 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|