diff options
31 files changed, 248 insertions, 615 deletions
diff --git a/distro/ruby_bin_folder/AMD64/Chef.PowerShell.Wrapper.dll b/distro/ruby_bin_folder/AMD64/Chef.PowerShell.Wrapper.dll Binary files differindex c411388674..022591ffd1 100644 --- a/distro/ruby_bin_folder/AMD64/Chef.PowerShell.Wrapper.dll +++ b/distro/ruby_bin_folder/AMD64/Chef.PowerShell.Wrapper.dll diff --git a/distro/ruby_bin_folder/AMD64/Chef.PowerShell.dll b/distro/ruby_bin_folder/AMD64/Chef.PowerShell.dll Binary files differindex ec80d05b33..2316fffdb5 100644 --- a/distro/ruby_bin_folder/AMD64/Chef.PowerShell.dll +++ b/distro/ruby_bin_folder/AMD64/Chef.PowerShell.dll diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll Binary files differindex 5334ee315e..cb9a5c276b 100644 --- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll +++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll Binary files differindex c74bec50a4..3f472f2c81 100644 --- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll +++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll diff --git a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb Binary files differindex 7ef67e5b43..03a0860086 100644 --- a/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb +++ b/distro/ruby_bin_folder/AMD64/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb diff --git a/distro/ruby_bin_folder/x86/Chef.PowerShell.dll b/distro/ruby_bin_folder/x86/Chef.PowerShell.dll Binary files differindex 2ffbbea92c..8a528ad226 100644 --- a/distro/ruby_bin_folder/x86/Chef.PowerShell.dll +++ b/distro/ruby_bin_folder/x86/Chef.PowerShell.dll diff --git a/distro/ruby_bin_folder/x86/Chef.Powershell.Wrapper.dll b/distro/ruby_bin_folder/x86/Chef.Powershell.Wrapper.dll Binary files differindex b72d13c9c0..203d243b37 100644 --- a/distro/ruby_bin_folder/x86/Chef.Powershell.Wrapper.dll +++ b/distro/ruby_bin_folder/x86/Chef.Powershell.Wrapper.dll diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll Binary files differindex 09e0fa1333..879df9ab6e 100644 --- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll +++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.PowerShell.Wrapper.Core.dll diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll Binary files differindex 6827b2a64c..ff50126c80 100644 --- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll +++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.dll diff --git a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb Binary files differindex 2546223f9d..5c979f1750 100644 --- a/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb +++ b/distro/ruby_bin_folder/x86/shared/Microsoft.NETCore.App/5.0.0/Chef.Powershell.Core.pdb diff --git a/lib/chef/mixin/powershell_exec.rb b/lib/chef/mixin/powershell_exec.rb index 03b058edcf..bbf8ae1a69 100644 --- a/lib/chef/mixin/powershell_exec.rb +++ b/lib/chef/mixin/powershell_exec.rb @@ -23,10 +23,12 @@ require_relative "../pwsh" # powershell_exec is initialized with a string that should be set to the script # to run and also takes an optional interpreter argument which must be either # :powershell (Windows PowerShell which is the default) or :pwsh (PowerShell -# Core). It will return a Chef::PowerShell object that provides 4 methods: +# Core). It will return a Chef::PowerShell object that provides 5 methods: # # .result - returns a hash representing the results returned by executing the # PowerShell script block +# .verbose - this is an array of string containing any messages written to the +# PowerShell verbose stream during execution # .errors - this is an array of string containing any messages written to the # PowerShell error stream during execution # .error? - returns true if there were error messages written to the PowerShell diff --git a/lib/chef/platform/query_helpers.rb b/lib/chef/platform/query_helpers.rb index 6188ce0cb5..bd0703d72a 100644 --- a/lib/chef/platform/query_helpers.rb +++ b/lib/chef/platform/query_helpers.rb @@ -58,10 +58,10 @@ class Chef end def dsc_refresh_mode_disabled?(node) - require_relative "../util/powershell/cmdlet" - cmdlet = Chef::Util::Powershell::Cmdlet.new(node, "Get-DscLocalConfigurationManager", :object) - metadata = cmdlet.run!.return_value - metadata["RefreshMode"] == "Disabled" + require_relative "../powershell" + exec = Chef::PowerShell.new("Get-DscLocalConfigurationManager") + exec.error! + exec.result["RefreshMode"] == "Disabled" end def supported_powershell_version?(node, version_string) diff --git a/lib/chef/powershell.rb b/lib/chef/powershell.rb index 6b925eabb6..b49d3c58e4 100644 --- a/lib/chef/powershell.rb +++ b/lib/chef/powershell.rb @@ -24,6 +24,7 @@ class Chef attr_reader :result attr_reader :errors + attr_reader :verbose # Run a command under PowerShell via FFI # This implementation requires the managed dll and native wrapper to be in the library search @@ -72,6 +73,7 @@ class Chef hashed_outcome = Chef::JSONCompat.parse(execution) @result = Chef::JSONCompat.parse(hashed_outcome["result"]) @errors = hashed_outcome["errors"] + @verbose = hashed_outcome["verbose"] end end end diff --git a/lib/chef/provider/dsc_resource.rb b/lib/chef/provider/dsc_resource.rb index 5f1f8ca8ac..a919d1deff 100644 --- a/lib/chef/provider/dsc_resource.rb +++ b/lib/chef/provider/dsc_resource.rb @@ -15,7 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # -require_relative "../util/powershell/cmdlet" +require "timeout" unless defined?(Timeout) +require_relative "../mixin/powershell_exec" require_relative "../util/dsc/local_configuration_manager" require_relative "../mixin/powershell_type_coercions" require_relative "../util/dsc/resource_store" @@ -130,27 +131,27 @@ class Chef def test_resource result = invoke_resource(:test) add_dsc_verbose_log(result) - return_dsc_resource_result(result, "InDesiredState") + result.result["InDesiredState"] end def set_resource result = invoke_resource(:set) add_dsc_verbose_log(result) - create_reboot_resource if return_dsc_resource_result(result, "RebootRequired") - result.return_value + create_reboot_resource if result.result["RebootRequired"] + result end def add_dsc_verbose_log(result) # We really want this information from the verbose stream, # however in some versions of WMF, Invoke-DscResource is not correctly # writing to that stream and instead just dumping to stdout - verbose_output = result.stream(:verbose) - verbose_output = result.stdout if verbose_output.empty? + verbose_output = result.verbose.join("\n") + verbose_output = result.result if verbose_output.empty? if @converge_description.nil? || @converge_description.empty? @converge_description = verbose_output else - @converge_description << "\n" + @converge_description << "\n\n" @converge_description << verbose_output end end @@ -159,26 +160,13 @@ class Chef @module_version.nil? ? module_name : "@{ModuleName='#{module_name}';ModuleVersion='#{@module_version}'}" end - def invoke_resource(method, output_format = :object) + def invoke_resource(method) properties = translate_type(new_resource.properties) switches = "-Method #{method} -Name #{new_resource.resource}"\ " -Property #{properties} -Module #{module_info_object} -Verbose" - cmdlet = Chef::Util::Powershell::Cmdlet.new( - node, - "Invoke-DscResource #{switches}", - output_format - ) - cmdlet.run!({}, { timeout: new_resource.timeout }) - end - - def return_dsc_resource_result(result, property_name) - if result.return_value.is_a?(Array) - # WMF Feb 2015 Preview - result.return_value[0][property_name] - else - # WMF April 2015 Preview - result.return_value[property_name] - end + Timeout.timeout(new_resource.timeout) { + powershell_exec!("Invoke-DscResource #{switches}") + } end def create_reboot_resource diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb index 8793a9274a..3e257492e7 100644 --- a/lib/chef/provider/dsc_script.rb +++ b/lib/chef/provider/dsc_script.rb @@ -16,7 +16,6 @@ # limitations under the License. # -require_relative "../util/powershell/cmdlet" require_relative "../util/dsc/configuration_generator" require_relative "../util/dsc/local_configuration_manager" require_relative "../util/path_helper" @@ -32,11 +31,11 @@ class Chef @dsc_resource = dsc_resource @resource_converged = false @operations = { - set: Proc.new do |config_manager, document, shellout_flags| - config_manager.set_configuration(document, shellout_flags) + set: Proc.new do |config_manager, document| + config_manager.set_configuration(document) end, - test: Proc.new do |config_manager, document, shellout_flags| - config_manager.test_configuration(document, shellout_flags) + test: Proc.new do |config_manager, document| + config_manager.test_configuration(document) end } end @@ -85,20 +84,24 @@ class Chef config_manager = Chef::Util::DSC::LocalConfigurationManager.new(@run_context.node, config_directory) - shellout_flags = { - cwd: @dsc_resource.cwd, - environment: @dsc_resource.environment, - timeout: @dsc_resource.timeout, - } + cwd = @dsc_resource.cwd || Dir.pwd + original_env = ENV.to_hash begin - configuration_document = generate_configuration_document(config_directory, configuration_flags) - @operations[operation].call(config_manager, configuration_document, shellout_flags) + ENV.update(@dsc_resource.environment || original_env) + Dir.chdir(cwd) do + Timeout.timeout(@dsc_resource.timeout) do + configuration_document = generate_configuration_document(config_directory, configuration_flags) + @operations[operation].call(config_manager, configuration_document) + end + end rescue Exception => e logger.error("DSC operation failed: #{e.message}") raise e ensure ::FileUtils.rm_rf(config_directory) + ENV.clear + ENV.update(original_env) end end @@ -112,20 +115,14 @@ class Chef end def generate_configuration_document(config_directory, configuration_flags) - shellout_flags = { - cwd: @dsc_resource.cwd, - environment: @dsc_resource.environment, - timeout: @dsc_resource.timeout, - } - generator = Chef::Util::DSC::ConfigurationGenerator.new(@run_context.node, config_directory) if @dsc_resource.command - generator.configuration_document_from_script_path(@dsc_resource.command, configuration_name, configuration_flags, shellout_flags) + generator.configuration_document_from_script_path(@dsc_resource.command, configuration_name, configuration_flags) else # If code is also not provided, we mimic what the other script resources do (execute nothing) logger.warn("Neither code or command were provided for dsc_resource[#{@dsc_resource.name}].") unless @dsc_resource.code - generator.configuration_document_from_script_code(@dsc_resource.code || "", configuration_flags, @dsc_resource.imports, shellout_flags) + generator.configuration_document_from_script_code(@dsc_resource.code || "", configuration_flags, @dsc_resource.imports) end end diff --git a/lib/chef/util/dsc/configuration_generator.rb b/lib/chef/util/dsc/configuration_generator.rb index 479e96dfa2..25b6110464 100644 --- a/lib/chef/util/dsc/configuration_generator.rb +++ b/lib/chef/util/dsc/configuration_generator.rb @@ -16,36 +16,34 @@ # limitations under the License. # -require_relative "../powershell/cmdlet" +require_relative "../../mixin/powershell_exec" class Chef::Util::DSC class ConfigurationGenerator + include Chef::Mixin::PowershellExec + def initialize(node, config_directory) @node = node @config_directory = config_directory end - def configuration_document_from_script_code(code, configuration_flags, imports, shellout_flags) + def configuration_document_from_script_code(code, configuration_flags, imports) Chef::Log.trace("DSC: DSC code:\n '#{code}'") generated_script_path = write_document_generation_script(code, "chef_dsc", imports) begin - configuration_document_from_script_path(generated_script_path, "chef_dsc", configuration_flags, shellout_flags) + configuration_document_from_script_path(generated_script_path, "chef_dsc", configuration_flags) ensure ::FileUtils.rm(generated_script_path) end end - def configuration_document_from_script_path(script_path, configuration_name, configuration_flags, shellout_flags) + def configuration_document_from_script_path(script_path, configuration_name, configuration_flags) validate_configuration_name!(configuration_name) - document_generation_cmdlet = Chef::Util::Powershell::Cmdlet.new( - @node, - configuration_document_generation_code(script_path, configuration_name) - ) - - merged_configuration_flags = get_merged_configuration_flags!(configuration_flags, configuration_name) + config_generation_code = configuration_document_generation_code(script_path, configuration_name) + switches_string = command_switches_string(get_merged_configuration_flags!(configuration_flags, configuration_name)) - document_generation_cmdlet.run!(merged_configuration_flags, shellout_flags) + powershell_exec!("#{config_generation_code} #{switches_string}") configuration_document_location = find_configuration_document(configuration_name) unless configuration_document_location @@ -59,6 +57,49 @@ class Chef::Util::DSC protected + def validate_switch_name!(switch_parameter_name) + if !!(switch_parameter_name =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false + raise ArgumentError, "`#{switch_parameter_name}` is not a valid PowerShell cmdlet switch parameter name" + end + end + + def escape_parameter_value(parameter_value) + parameter_value.gsub(/(`|'|"|#)/, '`\1') + end + + def escape_string_parameter_value(parameter_value) + "'#{escape_parameter_value(parameter_value)}'" + end + + def command_switches_string(switches) + command_switches = switches.map do |switch_name, switch_value| + if switch_name.class != Symbol + raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name}'. The switch must be specified as a Symbol'" + end + + validate_switch_name!(switch_name) + + switch_argument = "" + switch_present = true + + case switch_value + when Numeric, Float + switch_argument = switch_value.to_s + when FalseClass + switch_present = false + when TrueClass + when String + switch_argument = escape_string_parameter_value(switch_value) + else + raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`" + end + + switch_present ? ["-#{switch_name.to_s.downcase}", switch_argument].join(" ").strip : "" + end + + command_switches.join(" ") + end + # From PowerShell error help for the Configuration language element: # Standard names may only contain letters (a-z, A-Z), numbers (0-9), and underscore (_). # The name may not be null or empty, and should start with a letter. diff --git a/lib/chef/util/dsc/lcm_output_parser.rb b/lib/chef/util/dsc/lcm_output_parser.rb index 509f680bb4..cccf30c182 100644 --- a/lib/chef/util/dsc/lcm_output_parser.rb +++ b/lib/chef/util/dsc/lcm_output_parser.rb @@ -75,15 +75,16 @@ class Chef # def self.parse(lcm_output, test_dsc_configuration) + lcm_output ||= "" + lcm_output = lcm_output.split("\n") test_dsc_configuration ? test_dsc_parser(lcm_output) : what_if_parser(lcm_output) end def self.test_dsc_parser(lcm_output) - lcm_output ||= "" current_resource = {} resources = [] - lcm_output.lines.each do |line| + lcm_output.each do |line| op_action , op_value = line.strip.split(":") op_action&.strip! case op_action @@ -107,11 +108,10 @@ class Chef end def self.what_if_parser(lcm_output) - lcm_output ||= "" current_resource = {} resources = [] - lcm_output.lines.each do |line| + lcm_output.each do |line| op_action, op_type, info = parse_line(line) case op_action diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb index 431936574a..091d4aa426 100644 --- a/lib/chef/util/dsc/local_configuration_manager.rb +++ b/lib/chef/util/dsc/local_configuration_manager.rb @@ -16,25 +16,27 @@ # limitations under the License. # -require_relative "../powershell/cmdlet" +require_relative "../../mixin/powershell_exec" require_relative "lcm_output_parser" class Chef::Util::DSC class LocalConfigurationManager + include Chef::Mixin::PowershellExec + def initialize(node, configuration_path) @node = node @configuration_path = configuration_path clear_execution_time end - def test_configuration(configuration_document, shellout_flags) - status = run_configuration_cmdlet(configuration_document, false, shellout_flags) - log_dsc_exception(status.stderr) unless status.succeeded? - configuration_update_required?(status.return_value) + def test_configuration(configuration_document) + status = run_configuration_cmdlet(configuration_document, false) + log_dsc_exception(status.errors.join("\n")) if status.error? + configuration_update_required?(status.result) end - def set_configuration(configuration_document, shellout_flags) - run_configuration_cmdlet(configuration_document, true, shellout_flags) + def set_configuration(configuration_document) + run_configuration_cmdlet(configuration_document, true) end def last_operation_execution_time_seconds @@ -45,7 +47,7 @@ class Chef::Util::DSC private - def run_configuration_cmdlet(configuration_document, apply_configuration, shellout_flags) + def run_configuration_cmdlet(configuration_document, apply_configuration) Chef::Log.trace("DSC: Calling DSC Local Config Manager to #{apply_configuration ? "set" : "test"} configuration document.") start_operation_timing @@ -53,11 +55,12 @@ class Chef::Util::DSC begin save_configuration_document(configuration_document) - cmdlet = ::Chef::Util::Powershell::Cmdlet.new(@node, lcm_command(apply_configuration)) + cmd = lcm_command(apply_configuration) + Chef::Log.trace("DSC: Calling DSC Local Config Manager with:\n#{cmd}") + + status = powershell_exec(cmd) if apply_configuration - status = cmdlet.run!({}, shellout_flags) - else - status = cmdlet.run({}, shellout_flags) + status.error! end ensure end_operation_timing @@ -77,7 +80,7 @@ class Chef::Util::DSC ps4_base_command else if ps_version_gte_5? - "#{common_command_prefix} Test-DscConfiguration -path #{@configuration_path} | format-list" + "#{common_command_prefix} Test-DscConfiguration -path #{@configuration_path} | format-list | Out-String" else ps4_base_command + " -whatif; if (! $?) { exit 1 }" end @@ -100,7 +103,7 @@ class Chef::Util::DSC end def whatif_not_supported?(dsc_exception_output) - !! (dsc_exception_output.gsub(/[\r\n]+/, "").gsub(/\s+/, " ") =~ /A parameter cannot be found that matches parameter name 'Whatif'/i) + !! (dsc_exception_output.gsub(/[\n]+/, "").gsub(/\s+/, " ") =~ /A parameter cannot be found that matches parameter name 'Whatif'/i) end def dsc_module_import_failure?(command_output) diff --git a/lib/chef/util/dsc/resource_store.rb b/lib/chef/util/dsc/resource_store.rb index 772bc82468..49ca46832a 100644 --- a/lib/chef/util/dsc/resource_store.rb +++ b/lib/chef/util/dsc/resource_store.rb @@ -16,14 +16,14 @@ # limitations under the License. # -require_relative "../powershell/cmdlet" -require_relative "../powershell/cmdlet_result" +require_relative "../../mixin/powershell_exec" require_relative "../../exceptions" class Chef class Util class DSC class ResourceStore + include Chef::Mixin::PowershellExec def self.instance @@instance ||= ResourceStore.new.tap do |store| @@ -83,19 +83,13 @@ class Chef # Returns a list of dsc resources def query_resources - cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, "get-dscresource", - :object) - result = cmdlet.run - result.return_value + powershell_exec("get-dscresource").result end # Returns a list of dsc resources matching the provided name def query_resource(resource_name) - cmdlet = Chef::Util::Powershell::Cmdlet.new(nil, "get-dscresource #{resource_name}", - :object) - result = cmdlet.run - ret_val = result.return_value - if ret_val.nil? + ret_val = powershell_exec("get-dscresource #{resource_name}").result + if ret_val.empty? [] elsif ret_val.is_a? Array ret_val diff --git a/lib/chef/util/powershell/cmdlet.rb b/lib/chef/util/powershell/cmdlet.rb deleted file mode 100644 index 1c728fa424..0000000000 --- a/lib/chef/util/powershell/cmdlet.rb +++ /dev/null @@ -1,169 +0,0 @@ -# -# Author:: Adam Edwards (<adamed@chef.io>) -# -# Copyright:: Copyright (c) Chef Software Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -module Mixlib - autoload :ShellOut, "mixlib/shellout" -end -require_relative "../../mixin/windows_architecture_helper" -require_relative "cmdlet_result" - -class Chef - class Util - class Powershell - class Cmdlet - def initialize(node, cmdlet, output_format = nil, output_format_options = {}) - @output_format = output_format - @node = node - - case output_format - when nil, :text - @json_format = false - when :json, :object - @json_format = true - else - raise ArgumentError, "Invalid output format #{output_format} specified" - end - - @cmdlet = cmdlet - @output_format_options = output_format_options - end - - attr_reader :output_format - - def run(switches = {}, execution_options = {}, *arguments) - streams = { json: CmdletStream.new("json"), - verbose: CmdletStream.new("verbose"), - } - - arguments_string = arguments.join(" ") - - switches_string = command_switches_string(switches) - - json_depth = 5 - - if @json_format && @output_format_options.key?(:depth) - json_depth = @output_format_options[:depth] - end - - json_command = if @json_format - " | convertto-json -compress -depth #{json_depth} > #{streams[:json].path}" - else - "" - end - redirections = "4> '#{streams[:verbose].path}'" - command_string = "powershell.exe -executionpolicy bypass -noprofile -noninteractive "\ - "-command \"trap [Exception] {write-error -exception "\ - "($_.Exception.Message);exit 1};#{@cmdlet} #{switches_string} "\ - "#{arguments_string} #{redirections}"\ - "#{json_command}\";if ( ! $? ) { exit 1 }" - - augmented_options = { returns: [0], live_stream: false }.merge(execution_options) - command = Mixlib::ShellOut.new(command_string, augmented_options) - - status = nil - - with_os_architecture(@node) do - status = command.run_command - end - - CmdletResult.new(status, streams, @output_format) - end - - def run!(switches = {}, execution_options = {}, *arguments) - result = run(switches, execution_options, arguments) - - unless result.succeeded? - raise Chef::Exceptions::PowershellCmdletException, "PowerShell Cmdlet failed: #{result.stderr}" - end - - result - end - - protected - - include Chef::Mixin::WindowsArchitectureHelper - - def validate_switch_name!(switch_parameter_name) - if !!(switch_parameter_name =~ /\A[A-Za-z]+[_a-zA-Z0-9]*\Z/) == false - raise ArgumentError, "`#{switch_parameter_name}` is not a valid PowerShell cmdlet switch parameter name" - end - end - - def escape_parameter_value(parameter_value) - parameter_value.gsub(/(`|'|"|#)/, '`\1') - end - - def escape_string_parameter_value(parameter_value) - "'#{escape_parameter_value(parameter_value)}'" - end - - def command_switches_string(switches) - command_switches = switches.map do |switch_name, switch_value| - if switch_name.class != Symbol - raise ArgumentError, "Invalid type `#{switch_name} `for PowerShell switch '#{switch_name}'. The switch must be specified as a Symbol'" - end - - validate_switch_name!(switch_name) - - switch_argument = "" - switch_present = true - - case switch_value - when Numeric, Float - switch_argument = switch_value.to_s - when FalseClass - switch_present = false - when TrueClass - when String - switch_argument = escape_string_parameter_value(switch_value) - else - raise ArgumentError, "Invalid argument type `#{switch_value.class}` specified for PowerShell switch `:#{switch_name}`. Arguments to PowerShell must be of type `String`, `Numeric`, `Float`, `FalseClass`, or `TrueClass`" - end - - switch_present ? ["-#{switch_name.to_s.downcase}", switch_argument].join(" ").strip : "" - end - - command_switches.join(" ") - end - - class CmdletStream - def initialize(name) - @filename = Dir::Tmpname.create(name) {} - ObjectSpace.define_finalizer(self, self.class.destroy(@filename)) - end - - def path - @filename - end - - def read - if File.exist? @filename - File.open(@filename, "rb:bom|UTF-16LE") do |f| - f.read.encode("UTF-8") - end - end - end - - def self.destroy(name) - proc { File.delete(name) if File.exist? name } - end - end - end - end - end -end diff --git a/lib/chef/util/powershell/cmdlet_result.rb b/lib/chef/util/powershell/cmdlet_result.rb deleted file mode 100644 index 7aee2e8c4b..0000000000 --- a/lib/chef/util/powershell/cmdlet_result.rb +++ /dev/null @@ -1,61 +0,0 @@ -# -# Author:: Adam Edwards (<adamed@chef.io>) -# -# Copyright:: Copyright (c) Chef Software Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require_relative "../../json_compat" - -class Chef - class Util - class Powershell - class CmdletResult - attr_reader :output_format - - def initialize(status, streams, output_format) - @status = status - @output_format = output_format - @streams = streams - end - - def stdout - @status.stdout - end - - def stderr - @status.stderr - end - - def stream(name) - @streams[name].read - end - - def return_value - if output_format == :object - Chef::JSONCompat.parse(stream(:json)) - elsif output_format == :json - stream(:json) - else - @status.stdout - end - end - - def succeeded? - @succeeded = @status.status.exitstatus == 0 - end - end - end - end -end diff --git a/spec/functional/resource/dsc_script_spec.rb b/spec/functional/resource/dsc_script_spec.rb index 9d18e2f85d..60197c8643 100644 --- a/spec/functional/resource/dsc_script_spec.rb +++ b/spec/functional/resource/dsc_script_spec.rb @@ -261,10 +261,10 @@ describe Chef::Resource::DscScript, :windows_powershell_dsc_only do it "should raise an exception if the cwd is etc" do dsc_test_resource.cwd(dsc_environment_fail_etc_directory) - expect { dsc_test_resource.run_action(:run) }.to raise_error(Chef::Exceptions::PowershellCmdletException) + expect { dsc_test_resource.run_action(:run) }.to raise_error(Chef::PowerShell::CommandFailed) begin dsc_test_resource.run_action(:run) - rescue Chef::Exceptions::PowershellCmdletException => e + rescue Chef::PowerShell::CommandFailed => e expect(e.message).to match(exception_message_signature) end end diff --git a/spec/functional/util/powershell/cmdlet_spec.rb b/spec/functional/util/powershell/cmdlet_spec.rb deleted file mode 100644 index 565456f687..0000000000 --- a/spec/functional/util/powershell/cmdlet_spec.rb +++ /dev/null @@ -1,111 +0,0 @@ -# -# Author:: Adam Edwards (<adamed@chef.io>) -# -# Copyright:: Copyright (c) Chef Software Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require "chef/json_compat" -require "spec_helper" - -describe Chef::Util::Powershell::Cmdlet, :windows_powershell_dsc_only do - before(:all) do - @node = Chef::Node.new - @node.consume_external_attrs(OHAI_SYSTEM.data, {}) - end - let(:cmd_output_format) { :text } - let(:simple_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, "get-childitem", cmd_output_format, { depth: 2 }) } - let(:invalid_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, "get-idontexist", cmd_output_format) } - let(:cmdlet_get_item_requires_switch_or_argument) { Chef::Util::Powershell::Cmdlet.new(@node, "get-item", cmd_output_format, { depth: 2 }) } - let(:cmdlet_alias_requires_switch_or_argument) { Chef::Util::Powershell::Cmdlet.new(@node, "alias", cmd_output_format, { depth: 2 }) } - let(:etc_directory) { "#{ENV["systemroot"]}\\system32\\drivers\\etc" } - let(:architecture_cmdlet) { Chef::Util::Powershell::Cmdlet.new(@node, "$env:PROCESSOR_ARCHITECTURE") } - - it "executes a simple process" do - result = simple_cmdlet.run - expect(result.succeeded?).to eq(true) - end - - it "#run does not raise a PowershellCmdletException exception if the command cannot be executed" do - expect { invalid_cmdlet.run }.not_to raise_error - end - - it "#run! raises a PowershellCmdletException exception if the command cannot be executed" do - expect { invalid_cmdlet.run! }.to raise_error(Chef::Exceptions::PowershellCmdletException) - end - - it "executes a 64-bit command on a 64-bit OS, 32-bit otherwise" do - os_arch = ENV["PROCESSOR_ARCHITEW6432"] - if os_arch.nil? - os_arch = ENV["PROCESSOR_ARCHITECTURE"] - end - - result = architecture_cmdlet.run - execution_arch = result.return_value - execution_arch.strip! - expect(execution_arch).to eq(os_arch) - end - - it "passes command line switches to the command" do - result = cmdlet_alias_requires_switch_or_argument.run({ name: "ls" }) - expect(result.succeeded?).to eq(true) - end - - it "passes command line arguments to the command" do - result = cmdlet_alias_requires_switch_or_argument.run({}, {}, "ls") - expect(result.succeeded?).to eq(true) - end - - it "passes command line arguments and switches to the command" do - result = cmdlet_get_item_requires_switch_or_argument.run({ path: etc_directory }, {}, " | select-object -property fullname | format-table -hidetableheaders") - expect(result.succeeded?).to eq(true) - returned_directory = result.return_value - returned_directory.strip! - expect(returned_directory).to eq(etc_directory) - end - - it "passes execution options to the command" do - result = cmdlet_get_item_requires_switch_or_argument.run({}, { cwd: etc_directory }, ". | select-object -property fullname | format-table -hidetableheaders") - expect(result.succeeded?).to eq(true) - returned_directory = result.return_value - returned_directory.strip! - expect(returned_directory).to eq(etc_directory) - end - - context "when returning json" do - let(:cmd_output_format) { :json } - it "returns json format data" do - result = cmdlet_alias_requires_switch_or_argument.run({}, {}, "ls") - expect(result.succeeded?).to eq(true) - expect { Chef::JSONCompat.parse(result.return_value) }.not_to raise_error - end - end - - context "when returning Ruby objects" do - let(:cmd_output_format) { :object } - it "returns object format data" do - result = simple_cmdlet.run({}, { cwd: etc_directory }, "hosts") - expect(result.succeeded?).to eq(true) - data = result.return_value - expect(data["Name"]).to eq("hosts") - end - end - - context "when constructor is given invalid arguments" do - let(:cmd_output_format) { :invalid } - it "throws an exception if an invalid format is passed to the constructor" do - expect { simple_cmdlet }.to raise_error(ArgumentError) - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4c925bace3..17ce1ab5b7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -31,7 +31,7 @@ $LOAD_PATH.unshift File.expand_path("../chef-utils/lib", __dir__) require "rubygems" require "rspec/mocks" - +require "rexml/document" require "webmock/rspec" require "chef" diff --git a/spec/unit/mixin/powershell_exec_spec.rb b/spec/unit/mixin/powershell_exec_spec.rb index 245f681688..92e92dc2a1 100644 --- a/spec/unit/mixin/powershell_exec_spec.rb +++ b/spec/unit/mixin/powershell_exec_spec.rb @@ -66,7 +66,7 @@ describe Chef::Mixin::PowershellExec, :windows_only do execution = object.powershell_exec("this-should-error") expect(execution.errors).to be_a_kind_of(Array) expect(execution.errors[0]).to be_a_kind_of(String) - expect(execution.errors[0]).to include("Runtime exception: this-should-error") + expect(execution.errors[0]).to include("The term 'this-should-error' is not recognized") end it "raises an error if the interpreter is invalid" do diff --git a/spec/unit/platform/query_helpers_spec.rb b/spec/unit/platform/query_helpers_spec.rb index 728348e9af..0b4169810e 100644 --- a/spec/unit/platform/query_helpers_spec.rb +++ b/spec/unit/platform/query_helpers_spec.rb @@ -41,24 +41,23 @@ end describe "Chef::Platform#dsc_refresh_mode_disabled?" do let(:node) { instance_double("Chef::Node") } - let(:cmdlet) { instance_double("Chef::Util::Powershell::Cmdlet") } - let(:cmdlet_result) { instance_double("Chef::Util::Powershell::CmdletResult") } + let(:powershell) { instance_double("Chef::PowerShell") } it "returns true when RefreshMode is Disabled" do - expect(Chef::Util::Powershell::Cmdlet).to receive(:new) - .with(node, "Get-DscLocalConfigurationManager", :object) - .and_return(cmdlet) - expect(cmdlet).to receive(:run!).and_return(cmdlet_result) - expect(cmdlet_result).to receive(:return_value).and_return({ "RefreshMode" => "Disabled" }) + expect(Chef::PowerShell).to receive(:new) + .with("Get-DscLocalConfigurationManager") + .and_return(powershell) + expect(powershell).to receive(:error!) + expect(powershell).to receive(:result).and_return({ "RefreshMode" => "Disabled" }) expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be true end it "returns false when RefreshMode is not Disabled" do - expect(Chef::Util::Powershell::Cmdlet).to receive(:new) - .with(node, "Get-DscLocalConfigurationManager", :object) - .and_return(cmdlet) - expect(cmdlet).to receive(:run!).and_return(cmdlet_result) - expect(cmdlet_result).to receive(:return_value).and_return({ "RefreshMode" => "LaLaLa" }) + expect(Chef::PowerShell).to receive(:new) + .with("Get-DscLocalConfigurationManager") + .and_return(powershell) + expect(powershell).to receive(:error!) + expect(powershell).to receive(:result).and_return({ "RefreshMode" => "LaLaLa" }) expect(Chef::Platform.dsc_refresh_mode_disabled?(node)).to be false end end diff --git a/spec/unit/provider/dsc_resource_spec.rb b/spec/unit/provider/dsc_resource_spec.rb index 8613ce4af4..2540cb9df2 100644 --- a/spec/unit/provider/dsc_resource_spec.rb +++ b/spec/unit/provider/dsc_resource_spec.rb @@ -85,14 +85,13 @@ describe Chef::Provider::DscResource do node.automatic[:languages][:powershell][:version] = "5.0.10018.0" node end - let(:resource_result) { double("CmdletResult", return_value: { "InDesiredState" => true }, stream: "description") } - let(:invoke_dsc_resource) { double("cmdlet", run!: resource_result) } + let(:resource_result) { double("PowerShell", result: { "InDesiredState" => true }, verbose: ["description"]) } let(:store) { double("ResourceStore", find: resource_records) } let(:resource_records) { [] } before do allow(Chef::Util::DSC::ResourceStore).to receive(:instance).and_return(store) - allow(Chef::Util::Powershell::Cmdlet).to receive(:new).and_return(invoke_dsc_resource) + allow(provider).to receive(:powershell_exec!).and_return(resource_result) allow(provider).to receive(:dsc_refresh_mode_disabled?).and_return(true) end @@ -112,9 +111,8 @@ describe Chef::Provider::DscResource do it "flags the resource as reboot required when required" do expect(provider).to receive(:test_resource).and_return(false) expect(provider).to receive(:invoke_resource) - .and_return(double(stdout: "", return_value: nil)) + .and_return(double(result: { "RebootRequired" => true })) expect(provider).to receive(:add_dsc_verbose_log) - expect(provider).to receive(:return_dsc_resource_result).and_return(true) expect(provider).to receive(:create_reboot_resource) provider.run_action(:run) end @@ -122,9 +120,8 @@ describe Chef::Provider::DscResource do it "does not flag the resource as reboot required when not required" do expect(provider).to receive(:test_resource).and_return(false) expect(provider).to receive(:invoke_resource) - .and_return(double(stdout: "", return_value: nil)) + .and_return(double(stdout: "", result: {})) expect(provider).to receive(:add_dsc_verbose_log) - expect(provider).to receive(:return_dsc_resource_result).and_return(false) expect(provider).to_not receive(:create_reboot_resource) provider.run_action(:run) end @@ -142,9 +139,7 @@ describe Chef::Provider::DscResource do let(:resource_records) { [{}] } it "returns the default dsc resource module" do - expect(Chef::Util::Powershell::Cmdlet).to receive(:new) do |node, cmdlet, format| - expect(cmdlet).to match(/Module PSDesiredStateConfiguration /) - end.and_return(invoke_dsc_resource) + expect(provider).to receive(:powershell_exec!).with(/Module PSDesiredStateConfiguration /).and_return(resource_result) provider.run_action(:run) end end @@ -153,9 +148,7 @@ describe Chef::Provider::DscResource do let(:resource_records) { [{ "Module" => { "Name" => "ModuleName" } }] } it "returns the default dsc resource module" do - expect(Chef::Util::Powershell::Cmdlet).to receive(:new) do |node, cmdlet, format| - expect(cmdlet).to match(/Module ModuleName /) - end.and_return(invoke_dsc_resource) + expect(provider).to receive(:powershell_exec!).with(/Module ModuleName /).and_return(resource_result) provider.run_action(:run) end end @@ -286,8 +279,6 @@ describe Chef::Provider::DscResource do end describe "invoke_resource" do - let(:cmdlet) { double(run!: nil) } - before(:each) do allow(provider).to receive(:translate_type).and_return("my_properties") provider.instance_variable_set(:@new_resource, double( @@ -301,12 +292,8 @@ describe Chef::Provider::DscResource do end it "invokes Invoke-DscResource command with module name" do - expect(Chef::Util::Powershell::Cmdlet).to receive(:new).with( - node, - "Invoke-DscResource -Method my_method -Name my_resource -Property my_properties -Module my_module -Verbose", - "my_output_format" - ).and_return(cmdlet) - provider.send(:invoke_resource, "my_method", "my_output_format") + expect(provider).to receive(:powershell_exec!).with("Invoke-DscResource -Method my_method -Name my_resource -Property my_properties -Module my_module -Verbose").and_return(nil) + provider.send(:invoke_resource, "my_method") end end @@ -318,12 +305,8 @@ describe Chef::Provider::DscResource do end it "invokes Invoke-DscResource command with module info object" do - expect(Chef::Util::Powershell::Cmdlet).to receive(:new).with( - node, - "Invoke-DscResource -Method my_method -Name my_resource -Property my_properties -Module @{ModuleName='my_module';ModuleVersion='my_module_version'} -Verbose", - "my_output_format" - ).and_return(cmdlet) - provider.send(:invoke_resource, "my_method", "my_output_format") + expect(provider).to receive(:powershell_exec!).with("Invoke-DscResource -Method my_method -Name my_resource -Property my_properties -Module @{ModuleName='my_module';ModuleVersion='my_module_version'} -Verbose").and_return(nil) + provider.send(:invoke_resource, "my_method") end end end diff --git a/spec/unit/provider/dsc_script_spec.rb b/spec/unit/provider/dsc_script_spec.rb index f0a63e0a5b..d59b6f2480 100644 --- a/spec/unit/provider/dsc_script_spec.rb +++ b/spec/unit/provider/dsc_script_spec.rb @@ -99,7 +99,7 @@ describe Chef::Provider::DscScript do it "should noop if neither code or command are provided" do allow(provider).to receive(:load_current_resource) generator = double("Chef::Util::DSC::ConfigurationGenerator") - expect(generator).to receive(:configuration_document_from_script_code).with("", anything, anything, anything) + expect(generator).to receive(:configuration_document_from_script_code).with("", anything, anything) allow(Chef::Util::DSC::ConfigurationGenerator).to receive(:new).and_return(generator) provider.send(:generate_configuration_document, "tmp", nil) end diff --git a/spec/unit/util/dsc/configuration_generator_spec.rb b/spec/unit/util/dsc/configuration_generator_spec.rb index eee6adbd07..981b99af0e 100644 --- a/spec/unit/util/dsc/configuration_generator_spec.rb +++ b/spec/unit/util/dsc/configuration_generator_spec.rb @@ -25,6 +25,85 @@ describe Chef::Util::DSC::ConfigurationGenerator do Chef::Util::DSC::ConfigurationGenerator.new(node, "tmp") end + describe "#validate_switch_name!" do + it "should not raise an error if a name contains all upper case letters" do + conf_man.send(:validate_switch_name!, "HELLO") + end + + it "should not raise an error if the name contains all lower case letters" do + conf_man.send(:validate_switch_name!, "hello") + end + + it "should not raise an error if no special characters are used except _" do + conf_man.send(:validate_switch_name!, "hello_world") + end + + %w{! @ # $ % ^ & * & * ( ) - = + \{ \} . ? < > \\ /}.each do |sym| + it "raises an Argument error if it configuration name contains #{sym}" do + expect do + conf_man.send(:validate_switch_name!, "Hello#{sym}") + end.to raise_error(ArgumentError) + end + end + end + + describe "#escape_parameter_value" do + # Is this list really complete? + %w{` " # '}.each do |c| + it "escapse #{c}" do + expect(conf_man.send(:escape_parameter_value, "stuff #{c}")).to eql("stuff `#{c}") + end + end + + it "does not do anything to a string without special characters" do + expect(conf_man.send(:escape_parameter_value, "stuff")).to eql("stuff") + end + end + + describe "#escape_string_parameter_value" do + it "surrounds a string with ''" do + expect(conf_man.send(:escape_string_parameter_value, "stuff")).to eql("'stuff'") + end + end + + describe "#command_switches_string" do + it "raises an ArgumentError if the key is not a symbol" do + expect do + conf_man.send(:command_switches_string, { "foo" => "bar" }) + end.to raise_error(ArgumentError) + end + + it "does not allow invalid switch names" do + expect do + conf_man.send(:command_switches_string, { foo!: "bar" }) + end.to raise_error(ArgumentError) + end + + it "ignores switches with a false value" do + expect(conf_man.send(:command_switches_string, { foo: false })).to eql("") + end + + it "should correctly handle a value type of string" do + expect(conf_man.send(:command_switches_string, { foo: "bar" })).to eql("-foo 'bar'") + end + + it "should correctly handle a value type of string even when it is 0 length" do + expect(conf_man.send(:command_switches_string, { foo: "" })).to eql("-foo ''") + end + + it "should not quote integers" do + expect(conf_man.send(:command_switches_string, { foo: 1 })).to eql("-foo 1") + end + + it "should not quote floats" do + expect(conf_man.send(:command_switches_string, { foo: 1.0 })).to eql("-foo 1.0") + end + + it "has just the switch when the value is true" do + expect(conf_man.send(:command_switches_string, { foo: true })).to eql("-foo") + end + end + describe "#validate_configuration_name!" do it "should not raise an error if a name contains all upper case letters" do conf_man.send(:validate_configuration_name!, "HELLO") diff --git a/spec/unit/util/dsc/local_configuration_manager_spec.rb b/spec/unit/util/dsc/local_configuration_manager_spec.rb index e0f7c4ddaf..8adf778949 100644 --- a/spec/unit/util/dsc/local_configuration_manager_spec.rb +++ b/spec/unit/util/dsc/local_configuration_manager_spec.rb @@ -49,23 +49,22 @@ describe Chef::Util::DSC::LocalConfigurationManager do EOH end - let(:lcm_status) do - double("LCM cmdlet status", stderr: lcm_standard_error, return_value: lcm_standard_output, succeeded?: lcm_cmdlet_success) + let(:powershell) do + double("Chef::PowerShell", errors: lcm_errors, error?: !lcm_errors.empty?, result: lcm_result) end describe "test_configuration method invocation" do context "when interacting with the LCM using a PowerShell cmdlet" do before(:each) do - allow(lcm).to receive(:run_configuration_cmdlet).and_return(lcm_status) + allow(lcm).to receive(:run_configuration_cmdlet).and_return(powershell) allow(lcm).to receive(:ps_version_gte_5?).and_return(false) end context "that returns successfully" do - let(:lcm_standard_output) { normal_lcm_output } - let(:lcm_standard_error) { nil } - let(:lcm_cmdlet_success) { true } + let(:lcm_result) { normal_lcm_output } + let(:lcm_errors) { [] } it "successfully returns resource information for normally formatted output when cmdlet the cmdlet succeeds" do - test_configuration_result = lcm.test_configuration("config", {}) + test_configuration_result = lcm.test_configuration("config") expect(test_configuration_result.class).to be(Array) expect(test_configuration_result.length).to be > 0 expect(Chef::Log).not_to receive(:warn) @@ -73,13 +72,12 @@ describe Chef::Util::DSC::LocalConfigurationManager do end context "when running on PowerShell version 5" do - let(:lcm_standard_output) { normal_lcm_output } - let(:lcm_standard_error) { nil } - let(:lcm_cmdlet_success) { true } + let(:lcm_result) { normal_lcm_output } + let(:lcm_errors) { [] } it "successfully returns resource information for normally formatted output when cmdlet the cmdlet succeeds" do allow(lcm).to receive(:ps_version_gte_5?).and_return(true) - test_configuration_result = lcm.test_configuration("config", {}) + test_configuration_result = lcm.test_configuration("config") expect(test_configuration_result.class).to be(Array) expect(test_configuration_result.length).to be > 0 expect(Chef::Log).not_to receive(:warn) @@ -87,13 +85,12 @@ describe Chef::Util::DSC::LocalConfigurationManager do end context "when running on PowerShell version less than 5" do - let(:lcm_standard_output) { normal_lcm_output } - let(:lcm_standard_error) { nil } - let(:lcm_cmdlet_success) { true } + let(:lcm_result) { normal_lcm_output } + let(:lcm_errors) { [] } it "successfully returns resource information for normally formatted output when cmdlet the cmdlet succeeds" do allow(lcm).to receive(:ps_version_gte_5?).and_return(false) - test_configuration_result = lcm.test_configuration("config", {}) + test_configuration_result = lcm.test_configuration("config") expect(test_configuration_result.class).to be(Array) expect(test_configuration_result.length).to be > 0 expect(Chef::Log).not_to receive(:warn) @@ -104,10 +101,9 @@ describe Chef::Util::DSC::LocalConfigurationManager do let(:common_command_prefix) { "$ProgressPreference = 'SilentlyContinue';" } let(:ps4_base_command) { "#{common_command_prefix} Start-DscConfiguration -path tmp -wait -erroraction 'stop' -force" } let(:lcm_command_ps4) { ps4_base_command + " -whatif; if (! $?) { exit 1 }" } - let(:lcm_command_ps5) { "#{common_command_prefix} Test-DscConfiguration -path tmp | format-list" } - let(:lcm_standard_output) { normal_lcm_output } - let(:lcm_standard_error) { nil } - let(:lcm_cmdlet_success) { true } + let(:lcm_command_ps5) { "#{common_command_prefix} Test-DscConfiguration -path tmp | format-list | Out-String" } + let(:lcm_result) { normal_lcm_output } + let(:lcm_errors) { [] } it "successfully returns command when apply_configuration true" do expect(lcm.send(:lcm_command, true)).to eq(ps4_base_command) @@ -125,9 +121,8 @@ describe Chef::Util::DSC::LocalConfigurationManager do end context "that fails due to missing what-if switch in DSC resource cmdlet implementation" do - let(:lcm_standard_output) { "" } - let(:lcm_standard_error) { no_whatif_lcm_output } - let(:lcm_cmdlet_success) { false } + let(:lcm_result) { "" } + let(:lcm_errors) { [no_whatif_lcm_output] } it "returns true when passed to #whatif_not_supported?" do expect(lcm.send(:whatif_not_supported?, no_whatif_lcm_output)).to be_truthy @@ -137,40 +132,38 @@ describe Chef::Util::DSC::LocalConfigurationManager do expect(Chef::Log).to receive(:warn).at_least(:once) expect(lcm).to receive(:whatif_not_supported?).and_call_original test_configuration_result = nil - expect { test_configuration_result = lcm.test_configuration("config", {}) }.not_to raise_error + expect { test_configuration_result = lcm.test_configuration("config") }.not_to raise_error expect(test_configuration_result.class).to be(Array) end end context "that fails due to a DSC resource not being imported before StartDSCConfiguration -whatif is executed" do - let(:lcm_standard_output) { "" } - let(:lcm_standard_error) { dsc_resource_import_failure_output } - let(:lcm_cmdlet_success) { false } + let(:lcm_result) { "" } + let(:lcm_errors) { [dsc_resource_import_failure_output] } it "logs a warning if the message is formatted as expected when a resource import failure occurs" do expect(Chef::Log).to receive(:warn).at_least(:once) expect(lcm).to receive(:dsc_module_import_failure?).and_call_original test_configuration_result = nil - expect { test_configuration_result = lcm.test_configuration("config", {}) }.not_to raise_error + expect { test_configuration_result = lcm.test_configuration("config") }.not_to raise_error end it "returns a (possibly empty) array of ResourceInfo instances" do expect(Chef::Log).to receive(:warn).at_least(:once) test_configuration_result = nil - expect { test_configuration_result = lcm.test_configuration("config", {}) }.not_to raise_error + expect { test_configuration_result = lcm.test_configuration("config") }.not_to raise_error expect(test_configuration_result.class).to be(Array) end end context "that fails due to an unknown PowerShell cmdlet error" do - let(:lcm_standard_output) { "some output" } - let(:lcm_standard_error) { "Abort, Retry, Fail?" } - let(:lcm_cmdlet_success) { false } + let(:lcm_result) { "some output" } + let(:lcm_errors) { ["Abort, Retry, Fail?"] } it "logs a warning" do expect(Chef::Log).to receive(:warn).at_least(:once) expect(lcm).to receive(:dsc_module_import_failure?).and_call_original - expect { lcm.test_configuration("config", {}) }.not_to raise_error + expect { lcm.test_configuration("config") }.not_to raise_error end end end @@ -188,12 +181,11 @@ describe Chef::Util::DSC::LocalConfigurationManager do end end - describe "#run_configuration_cmdlet" do + describe "#run_configuration_cmdlet", :windows_powershell_dsc_only do context "when invalid dsc script is given" do it "raises exception" do configuration_document = "invalid-config" - shellout_flags = { cwd: nil, environment: nil, timeout: nil } - expect { lcm.send(:run_configuration_cmdlet, configuration_document, true, shellout_flags) }.to raise_error(Chef::Exceptions::PowershellCmdletException) + expect { lcm.send(:run_configuration_cmdlet, configuration_document, true) }.to raise_error(Chef::PowerShell::CommandFailed) end end end diff --git a/spec/unit/util/powershell/cmdlet_spec.rb b/spec/unit/util/powershell/cmdlet_spec.rb deleted file mode 100644 index 4dc6e2b85c..0000000000 --- a/spec/unit/util/powershell/cmdlet_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -# -# Author:: Jay Mundrawala <jdm@chef.io> -# Copyright:: Copyright (c) Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require "chef" -require "chef/util/powershell/cmdlet" - -describe Chef::Util::Powershell::Cmdlet do - before (:all) do - @node = Chef::Node.new - @cmdlet = Chef::Util::Powershell::Cmdlet.new(@node, "Some-Commandlet") - end - - describe "#validate_switch_name!" do - it "should not raise an error if a name contains all upper case letters" do - @cmdlet.send(:validate_switch_name!, "HELLO") - end - - it "should not raise an error if the name contains all lower case letters" do - @cmdlet.send(:validate_switch_name!, "hello") - end - - it "should not raise an error if no special characters are used except _" do - @cmdlet.send(:validate_switch_name!, "hello_world") - end - - %w{! @ # $ % ^ & * & * ( ) - = + \{ \} . ? < > \\ /}.each do |sym| - it "raises an Argument error if it configuration name contains #{sym}" do - expect do - @cmdlet.send(:validate_switch_name!, "Hello#{sym}") - end.to raise_error(ArgumentError) - end - end - end - - describe "#escape_parameter_value" do - # Is this list really complete? - %w{` " # '}.each do |c| - it "escapse #{c}" do - expect(@cmdlet.send(:escape_parameter_value, "stuff #{c}")).to eql("stuff `#{c}") - end - end - - it "does not do anything to a string without special characters" do - expect(@cmdlet.send(:escape_parameter_value, "stuff")).to eql("stuff") - end - end - - describe "#escape_string_parameter_value" do - it "surrounds a string with ''" do - expect(@cmdlet.send(:escape_string_parameter_value, "stuff")).to eql("'stuff'") - end - end - - describe "#command_switches_string" do - it "raises an ArgumentError if the key is not a symbol" do - expect do - @cmdlet.send(:command_switches_string, { "foo" => "bar" }) - end.to raise_error(ArgumentError) - end - - it "does not allow invalid switch names" do - expect do - @cmdlet.send(:command_switches_string, { foo!: "bar" }) - end.to raise_error(ArgumentError) - end - - it "ignores switches with a false value" do - expect(@cmdlet.send(:command_switches_string, { foo: false })).to eql("") - end - - it "should correctly handle a value type of string" do - expect(@cmdlet.send(:command_switches_string, { foo: "bar" })).to eql("-foo 'bar'") - end - - it "should correctly handle a value type of string even when it is 0 length" do - expect(@cmdlet.send(:command_switches_string, { foo: "" })).to eql("-foo ''") - end - - it "should not quote integers" do - expect(@cmdlet.send(:command_switches_string, { foo: 1 })).to eql("-foo 1") - end - - it "should not quote floats" do - expect(@cmdlet.send(:command_switches_string, { foo: 1.0 })).to eql("-foo 1.0") - end - - it "has just the switch when the value is true" do - expect(@cmdlet.send(:command_switches_string, { foo: true })).to eql("-foo") - end - end -end |