Merge pull request #3530 from mitchellh/linux-command-filters-for-winrm-communicator
communicator/winrm: command filters for winrm communicator
This commit is contained in:
commit
f36dfb2961
|
@ -0,0 +1,48 @@
|
|||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
|
||||
# Handles loading and applying all available WinRM command filters
|
||||
class CommandFilter
|
||||
|
||||
@@cmd_filters = [
|
||||
"cat",
|
||||
"chmod",
|
||||
"chown",
|
||||
"grep",
|
||||
"rm",
|
||||
"test",
|
||||
"uname",
|
||||
"which"
|
||||
]
|
||||
|
||||
# Filter the given Vagrant command to ensure compatibility with Windows
|
||||
#
|
||||
# @param [String] The Vagrant shell command
|
||||
# @returns [String] Windows runnable command or empty string
|
||||
def filter(command)
|
||||
command_filters.each { |c| command = c.filter(command) if c.accept?(command) }
|
||||
command
|
||||
end
|
||||
|
||||
# All the available Linux command filters
|
||||
#
|
||||
# @returns [Array] All Linux command filter instances
|
||||
def command_filters
|
||||
@command_filters ||= create_command_filters()
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_command_filters
|
||||
[].tap do |filters|
|
||||
@@cmd_filters.each do |cmd|
|
||||
require_relative "command_filters/#{cmd}"
|
||||
class_name = "VagrantPlugins::CommunicatorWinRM::CommandFilters::#{cmd.capitalize}"
|
||||
filters << Module.const_get(class_name).new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
module CommandFilters
|
||||
|
||||
# Handles the special case of determining the guest OS using cat
|
||||
class Cat
|
||||
|
||||
def filter(command)
|
||||
# cat /etc/release | grep -i OmniOS
|
||||
# cat /etc/redhat-release
|
||||
# cat /etc/issue | grep 'Core Linux'
|
||||
# cat /etc/release | grep -i SmartOS
|
||||
''
|
||||
end
|
||||
|
||||
def accept?(command)
|
||||
# cat works in PowerShell, however we don't want to run Guest
|
||||
# OS detection as this will fail on Windows because the lack of the
|
||||
# grep command
|
||||
command.start_with?('cat /etc/')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
module CommandFilters
|
||||
|
||||
# Converts a *nix 'chmod' command to a PowerShell equivalent
|
||||
class Chmod
|
||||
|
||||
def filter(command)
|
||||
# Not support on Windows, the communicator will skip this command
|
||||
''
|
||||
end
|
||||
|
||||
def accept?(command)
|
||||
command.start_with?('chmod ')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
module CommandFilters
|
||||
|
||||
# Converts a *nix 'chown' command to a PowerShell equivalent
|
||||
class Chown
|
||||
|
||||
def filter(command)
|
||||
# Not support on Windows, the communicator will skip this command
|
||||
''
|
||||
end
|
||||
|
||||
def accept?(command)
|
||||
command.start_with?('chown ')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
module CommandFilters
|
||||
|
||||
# Converts a *nix 'grep' command to a PowerShell equivalent
|
||||
class Grep
|
||||
|
||||
def filter(command)
|
||||
# grep 'Fedora release [12][67890]' /etc/redhat-release
|
||||
# grep Funtoo /etc/gentoo-release
|
||||
# grep Gentoo /etc/gentoo-release
|
||||
|
||||
# grep is often used to detect the guest type in Vagrant, so don't bother running
|
||||
# to speed up OS detection
|
||||
''
|
||||
end
|
||||
|
||||
def accept?(command)
|
||||
command.start_with?('grep ')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
module CommandFilters
|
||||
|
||||
# Converts a *nix 'rm' command to a PowerShell equivalent
|
||||
class Rm
|
||||
|
||||
def filter(command)
|
||||
# rm -Rf /some/dir
|
||||
# rm /some/dir
|
||||
cmd_parts = command.strip.split(/\s+/)
|
||||
dir = cmd_parts[1]
|
||||
if dir == '-Rf'
|
||||
dir = cmd_parts[2]
|
||||
return "rm '#{dir}' -recurse -force"
|
||||
end
|
||||
return "rm '#{dir}' -force"
|
||||
end
|
||||
|
||||
def accept?(command)
|
||||
command.start_with?('rm ')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,68 @@
|
|||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
module CommandFilters
|
||||
|
||||
# Converts a *nix 'test' command to a PowerShell equivalent
|
||||
class Test
|
||||
|
||||
def filter(command)
|
||||
# test -d /tmp/dir
|
||||
# test -f /tmp/afile
|
||||
# test -L /somelink
|
||||
# test -x /tmp/some.exe
|
||||
|
||||
cmd_parts = command.strip.split(/\s+/)
|
||||
flag = cmd_parts[1]
|
||||
path = cmd_parts[2]
|
||||
|
||||
if flag == '-d'
|
||||
check_for_directory(path)
|
||||
elsif flag == '-f' || flag == '-x'
|
||||
check_for_file(path)
|
||||
else
|
||||
check_exists(path)
|
||||
end
|
||||
end
|
||||
|
||||
def accept?(command)
|
||||
command.start_with?("test ")
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def check_for_directory(path)
|
||||
<<-EOH
|
||||
$p = "#{path}"
|
||||
if ((Test-Path $p) -and (get-item $p).PSIsContainer) {
|
||||
exit 0
|
||||
}
|
||||
exit 1
|
||||
EOH
|
||||
end
|
||||
|
||||
def check_for_file(path)
|
||||
<<-EOH
|
||||
$p = "#{path}"
|
||||
if ((Test-Path $p) -and (!(get-item $p).PSIsContainer)) {
|
||||
exit 0
|
||||
}
|
||||
exit 1
|
||||
EOH
|
||||
end
|
||||
|
||||
def check_exists(path)
|
||||
<<-EOH
|
||||
$p = "#{path}"
|
||||
if (Test-Path $p) {
|
||||
exit 0
|
||||
}
|
||||
exit 1
|
||||
EOH
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
module CommandFilters
|
||||
|
||||
# Converts a *nix 'uname' command to a PowerShell equivalent
|
||||
class Uname
|
||||
|
||||
def filter(command)
|
||||
# uname -s | grep 'Darwin'
|
||||
# uname -s | grep VMkernel
|
||||
# uname -s | grep 'FreeBSD'
|
||||
# uname -s | grep 'Linux'
|
||||
# uname -s | grep NetBSD
|
||||
# uname -s | grep 'OpenBSD'
|
||||
# uname -sr | grep SunOS | grep -v 5.11
|
||||
# uname -sr | grep 'SunOS 5.11'
|
||||
|
||||
# uname is used to detect the guest type in Vagrant, so don't bother running
|
||||
# to speed up OS detection
|
||||
''
|
||||
end
|
||||
|
||||
def accept?(command)
|
||||
command.start_with?('uname ')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
module CommandFilters
|
||||
|
||||
# Converts a *nix 'which' command to a PowerShell equivalent
|
||||
class Which
|
||||
|
||||
def filter(command)
|
||||
executable = command.strip.split(/\s+/)[1]
|
||||
return <<-EOH
|
||||
$command = [Array](Get-Command #{executable} -errorAction SilentlyContinue)
|
||||
if ($null -eq $command) { exit 1 }
|
||||
write-host $command[0].Definition
|
||||
exit 0
|
||||
EOH
|
||||
end
|
||||
|
||||
def accept?(command)
|
||||
command.start_with?('which ')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@ require "log4r"
|
|||
|
||||
require_relative "helper"
|
||||
require_relative "shell"
|
||||
require_relative "command_filter"
|
||||
|
||||
module VagrantPlugins
|
||||
module CommunicatorWinRM
|
||||
|
@ -16,9 +17,10 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def initialize(machine)
|
||||
@machine = machine
|
||||
@logger = Log4r::Logger.new("vagrant::communication::winrm")
|
||||
@shell = nil
|
||||
@machine = machine
|
||||
@shell = nil
|
||||
@logger = Log4r::Logger.new("vagrant::communication::winrm")
|
||||
@cmd_filter = CommandFilter.new()
|
||||
|
||||
@logger.info("Initializing WinRMCommunicator")
|
||||
end
|
||||
|
@ -50,6 +52,10 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def execute(command, opts={}, &block)
|
||||
# If this is a *nix command with no Windows equivilant, don't run it
|
||||
command = @cmd_filter.filter(command)
|
||||
return 0 if command.empty?
|
||||
|
||||
opts = {
|
||||
:error_check => true,
|
||||
:error_class => Errors::ExecutionError,
|
||||
|
@ -58,26 +64,15 @@ module VagrantPlugins
|
|||
:shell => :powershell
|
||||
}.merge(opts || {})
|
||||
|
||||
if opts[:shell] == :powershell
|
||||
script = File.expand_path("../scripts/command_alias.ps1", __FILE__)
|
||||
script = File.read(script)
|
||||
command = script << "\r\n" << command << "\r\nexit $LASTEXITCODE"
|
||||
end
|
||||
|
||||
output = shell.send(opts[:shell], command, &block)
|
||||
|
||||
return output if opts[:shell] == :wql
|
||||
exitcode = output[:exitcode]
|
||||
raise_execution_error(opts, exitcode) if opts[:error_check] && exitcode != 0
|
||||
exitcode
|
||||
execution_output(output, opts)
|
||||
end
|
||||
alias_method :sudo, :execute
|
||||
|
||||
def test(command, opts=nil)
|
||||
@logger.info("Testing: #{command}")
|
||||
|
||||
# HACK: to speed up Vagrant 1.2 OS detection, skip checking for *nix OS
|
||||
return false unless (command =~ /^uname|^cat \/etc|^cat \/proc|grep 'Fedora/).nil?
|
||||
# If this is a *nix command with no Windows equivilant, assume failure
|
||||
command = @cmd_filter.filter(command)
|
||||
return false if command.empty?
|
||||
|
||||
opts = { :error_check => false }.merge(opts || {})
|
||||
execute(command, opts) == 0
|
||||
|
@ -111,10 +106,21 @@ module VagrantPlugins
|
|||
)
|
||||
end
|
||||
|
||||
def raise_execution_error(opts, exit_code)
|
||||
# Handles the raw WinRM shell result and converts it to a
|
||||
# standard Vagrant communicator result
|
||||
def execution_output(output, opts)
|
||||
if opts[:shell] == :wql
|
||||
return output
|
||||
elsif opts[:error_check] && output[:exitcode] != 0
|
||||
raise_execution_error(output, opts)
|
||||
end
|
||||
output[:exitcode]
|
||||
end
|
||||
|
||||
def raise_execution_error(output, opts)
|
||||
# The error classes expect the translation key to be _key, but that makes for an ugly
|
||||
# configuration parameter, so we set it here from `error_key`
|
||||
msg = "Command execution failed with an exit code of #{exit_code}"
|
||||
msg = "Command execution failed with an exit code of #{output[:exitcode]}"
|
||||
error_opts = opts.merge(:_key => opts[:error_key], :message => msg)
|
||||
raise opts[:error_class], error_opts
|
||||
end
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
function which {
|
||||
$command = [Array](Get-Command $args[0] -errorAction SilentlyContinue)
|
||||
if($null -eq $command)
|
||||
{
|
||||
exit 1
|
||||
}
|
||||
write-host $command[0].Definition
|
||||
exit 0
|
||||
}
|
||||
|
||||
function test ([Switch] $d, [String] $path) {
|
||||
if(Test-Path $path)
|
||||
{
|
||||
exit 0
|
||||
}
|
||||
exit 1
|
||||
}
|
||||
|
||||
function chmod {
|
||||
exit 0
|
||||
}
|
||||
|
||||
function chown {
|
||||
exit 0
|
||||
}
|
||||
|
||||
function mkdir ([Switch] $p, [String] $path)
|
||||
{
|
||||
if(Test-Path $path)
|
||||
{
|
||||
exit 0
|
||||
} else {
|
||||
New-Item $path -Type Directory -Force | Out-Null
|
||||
}
|
||||
}
|
|
@ -52,6 +52,9 @@ module VagrantPlugins
|
|||
end
|
||||
|
||||
def powershell(command, &block)
|
||||
# ensure an exit code
|
||||
command << "\r\n"
|
||||
command << "if ($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 0 }"
|
||||
execute_shell(command, :powershell, &block)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
require File.expand_path("../../../../base", __FILE__)
|
||||
|
||||
require Vagrant.source_root.join("plugins/communicators/winrm/command_filter")
|
||||
|
||||
describe VagrantPlugins::CommunicatorWinRM::CommandFilter, :unit => true do
|
||||
|
||||
describe '.command_filters' do
|
||||
it 'initializes all command filters in command filters directory' do
|
||||
expect(subject.command_filters()).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '.filter' do
|
||||
it 'filters out uname commands' do
|
||||
expect(subject.filter('uname -s stuff')).to eq('')
|
||||
end
|
||||
|
||||
it 'filters out grep commands' do
|
||||
expect(subject.filter("grep 'Fedora release [12][67890]' /etc/redhat-release")).to eq("")
|
||||
end
|
||||
|
||||
it 'filters out which commands' do
|
||||
expect(subject.filter('which ruby')).to include(
|
||||
'[Array](Get-Command ruby -errorAction SilentlyContinue)')
|
||||
end
|
||||
|
||||
it 'filters out test -d commands' do
|
||||
expect(subject.filter('test -d /tmp/dir')).to include(
|
||||
"$p = \"/tmp/dir\"")
|
||||
expect(subject.filter('test -d /tmp/dir')).to include(
|
||||
"if ((Test-Path $p) -and (get-item $p).PSIsContainer) {")
|
||||
end
|
||||
|
||||
it 'filters out test -f commands' do
|
||||
expect(subject.filter('test -f /tmp/file.txt')).to include(
|
||||
"$p = \"/tmp/file.txt\"")
|
||||
expect(subject.filter('test -f /tmp/file.txt')).to include(
|
||||
"if ((Test-Path $p) -and (!(get-item $p).PSIsContainer)) {")
|
||||
end
|
||||
|
||||
it 'filters out test -x commands' do
|
||||
expect(subject.filter('test -x /tmp/file.txt')).to include(
|
||||
"$p = \"/tmp/file.txt\"")
|
||||
expect(subject.filter('test -x /tmp/file.txt')).to include(
|
||||
"if ((Test-Path $p) -and (!(get-item $p).PSIsContainer)) {")
|
||||
end
|
||||
|
||||
it 'filters out other test commands' do
|
||||
expect(subject.filter('test -L /tmp/file.txt')).to include(
|
||||
"$p = \"/tmp/file.txt\"")
|
||||
expect(subject.filter('test -L /tmp/file.txt')).to include(
|
||||
"if (Test-Path $p) {")
|
||||
end
|
||||
|
||||
it 'filters out rm -Rf commands' do
|
||||
expect(subject.filter('rm -Rf /some/dir')).to eq(
|
||||
"rm '/some/dir' -recurse -force")
|
||||
end
|
||||
|
||||
it 'filters out rm commands' do
|
||||
expect(subject.filter('rm /some/dir')).to eq(
|
||||
"rm '/some/dir' -force")
|
||||
end
|
||||
|
||||
it 'filters out chown commands' do
|
||||
expect(subject.filter("chown -R root '/tmp/dir'")).to eq('')
|
||||
end
|
||||
|
||||
it 'filters out chmod commands' do
|
||||
expect(subject.filter("chmod 0600 ~/.ssh/authorized_keys")).to eq('')
|
||||
end
|
||||
|
||||
it 'filters out certain cat commands' do
|
||||
expect(subject.filter("cat /etc/release | grep -i OmniOS")).to eq('')
|
||||
end
|
||||
|
||||
it 'should not filter out other cat commands' do
|
||||
expect(subject.filter("cat /tmp/somefile")).to eq('cat /tmp/somefile')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -15,19 +15,19 @@ describe VagrantPlugins::CommunicatorWinRM::WinRMShell do
|
|||
|
||||
describe ".powershell" do
|
||||
it "should call winrm powershell" do
|
||||
expect(session).to receive(:powershell).with("dir").and_return({ exitcode: 0 })
|
||||
expect(session).to receive(:powershell).with(/^dir.+/).and_return({ exitcode: 0 })
|
||||
expect(subject.powershell("dir")[:exitcode]).to eq(0)
|
||||
end
|
||||
|
||||
it "should raise auth error when exception message contains 401" do
|
||||
expect(session).to receive(:powershell).with("dir").and_raise(
|
||||
expect(session).to receive(:powershell).with(/^dir.+/).and_raise(
|
||||
StandardError.new("Oh no! a 401 SOAP error!"))
|
||||
expect { subject.powershell("dir") }.to raise_error(
|
||||
VagrantPlugins::CommunicatorWinRM::Errors::AuthError)
|
||||
end
|
||||
|
||||
it "should raise an execution error when an exception occurs" do
|
||||
expect(session).to receive(:powershell).with("dir").and_raise(
|
||||
expect(session).to receive(:powershell).with(/^dir.+/).and_raise(
|
||||
StandardError.new("Oh no! a 500 SOAP error!"))
|
||||
expect { subject.powershell("dir") }.to raise_error(
|
||||
VagrantPlugins::CommunicatorWinRM::Errors::ExecutionError)
|
||||
|
|
Loading…
Reference in New Issue