From 96ab8f60c5d531cedb00307e4816af465f621b2a Mon Sep 17 00:00:00 2001 From: Shawn Neal Date: Wed, 23 Apr 2014 17:50:20 -0700 Subject: [PATCH] Added WinRM command filters These will be used to replace the guest side command_alias script that is sent with every communicator execute call. This avoids some uncessary remote calls to the guest, makes it unit testable, and allows larger PowerShell commands to be invoked. --- plugins/communicators/winrm/command_filter.rb | 47 +++++++++++++ .../winrm/command_filters/cat.rb | 27 +++++++ .../winrm/command_filters/chmod.rb | 21 ++++++ .../winrm/command_filters/chown.rb | 21 ++++++ .../communicators/winrm/command_filters/rm.rb | 28 ++++++++ .../winrm/command_filters/test.rb | 37 ++++++++++ .../winrm/command_filters/uname.rb | 31 ++++++++ .../winrm/command_filters/which.rb | 26 +++++++ .../winrm/command_filter_test.rb | 70 +++++++++++++++++++ 9 files changed, 308 insertions(+) create mode 100644 plugins/communicators/winrm/command_filter.rb create mode 100644 plugins/communicators/winrm/command_filters/cat.rb create mode 100644 plugins/communicators/winrm/command_filters/chmod.rb create mode 100644 plugins/communicators/winrm/command_filters/chown.rb create mode 100644 plugins/communicators/winrm/command_filters/rm.rb create mode 100644 plugins/communicators/winrm/command_filters/test.rb create mode 100644 plugins/communicators/winrm/command_filters/uname.rb create mode 100644 plugins/communicators/winrm/command_filters/which.rb create mode 100644 test/unit/plugins/communicators/winrm/command_filter_test.rb diff --git a/plugins/communicators/winrm/command_filter.rb b/plugins/communicators/winrm/command_filter.rb new file mode 100644 index 000000000..dcae3d1fc --- /dev/null +++ b/plugins/communicators/winrm/command_filter.rb @@ -0,0 +1,47 @@ +module VagrantPlugins + module CommunicatorWinRM + + # Handles loading and applying all available WinRM command filters + class CommandFilter + + @@cmd_filters = [ + "cat", + "chmod", + "chown", + "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 diff --git a/plugins/communicators/winrm/command_filters/cat.rb b/plugins/communicators/winrm/command_filters/cat.rb new file mode 100644 index 000000000..5d9cf8e01 --- /dev/null +++ b/plugins/communicators/winrm/command_filters/cat.rb @@ -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 diff --git a/plugins/communicators/winrm/command_filters/chmod.rb b/plugins/communicators/winrm/command_filters/chmod.rb new file mode 100644 index 000000000..a336f5dd4 --- /dev/null +++ b/plugins/communicators/winrm/command_filters/chmod.rb @@ -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 diff --git a/plugins/communicators/winrm/command_filters/chown.rb b/plugins/communicators/winrm/command_filters/chown.rb new file mode 100644 index 000000000..127731ff6 --- /dev/null +++ b/plugins/communicators/winrm/command_filters/chown.rb @@ -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 diff --git a/plugins/communicators/winrm/command_filters/rm.rb b/plugins/communicators/winrm/command_filters/rm.rb new file mode 100644 index 000000000..a0078c83d --- /dev/null +++ b/plugins/communicators/winrm/command_filters/rm.rb @@ -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 diff --git a/plugins/communicators/winrm/command_filters/test.rb b/plugins/communicators/winrm/command_filters/test.rb new file mode 100644 index 000000000..d837ece0c --- /dev/null +++ b/plugins/communicators/winrm/command_filters/test.rb @@ -0,0 +1,37 @@ +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+/) + if cmd_parts[1] == '-d' + # ensure it exists and is a directory + return "if ((Test-Path '#{cmd_parts[2]}') -and (get-item '#{cmd_parts[2]}').PSIsContainer) { exit 0 } exit 1" + elsif cmd_parts[1] == '-f' || cmd_parts[1] == '-x' + # ensure it exists and is a file + return "if ((Test-Path '#{cmd_parts[2]}') -and (!(get-item '#{cmd_parts[2]}').PSIsContainer)) { exit 0 } exit 1" + end + + # otherwise, just check for existence + return "if (Test-Path '#{cmd_parts[2]}') { exit 0 } exit 1" + end + + # if (Test-Path 'c:\windows' && (get-item 'c:\windows').PSIsContainer) { Write-Host 0 } Write-Host 1 + + def accept?(command) + command.start_with?('test ') + end + + end + + end + end +end diff --git a/plugins/communicators/winrm/command_filters/uname.rb b/plugins/communicators/winrm/command_filters/uname.rb new file mode 100644 index 000000000..4a8b0836d --- /dev/null +++ b/plugins/communicators/winrm/command_filters/uname.rb @@ -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 diff --git a/plugins/communicators/winrm/command_filters/which.rb b/plugins/communicators/winrm/command_filters/which.rb new file mode 100644 index 000000000..cf046ca19 --- /dev/null +++ b/plugins/communicators/winrm/command_filters/which.rb @@ -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 diff --git a/test/unit/plugins/communicators/winrm/command_filter_test.rb b/test/unit/plugins/communicators/winrm/command_filter_test.rb new file mode 100644 index 000000000..71010154d --- /dev/null +++ b/test/unit/plugins/communicators/winrm/command_filter_test.rb @@ -0,0 +1,70 @@ +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 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 eq( + "if ((Test-Path '/tmp/dir') -and (get-item '/tmp/dir').PSIsContainer) { exit 0 } exit 1") + end + + it 'filters out test -f commands' do + expect(subject.filter('test -f /tmp/file.txt')).to eq( + "if ((Test-Path '/tmp/file.txt') -and (!(get-item '/tmp/file.txt').PSIsContainer)) { exit 0 } exit 1") + end + + it 'filters out test -x commands' do + expect(subject.filter('test -x /tmp/file.txt')).to eq( + "if ((Test-Path '/tmp/file.txt') -and (!(get-item '/tmp/file.txt').PSIsContainer)) { exit 0 } exit 1") + end + + it 'filters out other test commands' do + expect(subject.filter('test -L /tmp/file.txt')).to eq( + "if (Test-Path '/tmp/file.txt') { exit 0 } exit 1") + 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