diff options
Diffstat (limited to 'lib/chef/util')
-rw-r--r-- | lib/chef/util/dsc/configuration_generator.rb | 63 | ||||
-rw-r--r-- | lib/chef/util/dsc/lcm_output_parser.rb | 8 | ||||
-rw-r--r-- | lib/chef/util/dsc/local_configuration_manager.rb | 31 | ||||
-rw-r--r-- | lib/chef/util/dsc/resource_store.rb | 16 | ||||
-rw-r--r-- | lib/chef/util/powershell/cmdlet.rb | 169 | ||||
-rw-r--r-- | lib/chef/util/powershell/cmdlet_result.rb | 61 |
6 files changed, 78 insertions, 270 deletions
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 |