diff options
author | piyushawasthi <piyush.awasthi@msystechnologies.com> | 2017-10-04 20:34:04 +0530 |
---|---|---|
committer | Bryan McLellan <btm@loftninjas.org> | 2017-10-16 21:38:49 -0400 |
commit | 4a080d5737912076d97632937d5b5126edc33c71 (patch) | |
tree | 9bb93cebcfe122c39f344044cbfa4a743c99c8a0 | |
parent | 81ca24afdef21b96f7fe349f08ffde00e090bf94 (diff) | |
download | chef-4a080d5737912076d97632937d5b5126edc33c71.tar.gz |
MSYS-684: Added parser for DSC configurationbtm/chef-12-dsc
Signed-off-by: piyushawasthi <piyush.awasthi@msystechnologies.com>
-rw-r--r-- | lib/chef/provider/dsc_script.rb | 6 | ||||
-rw-r--r-- | lib/chef/util/dsc/lcm_output_parser.rb | 59 | ||||
-rw-r--r-- | lib/chef/util/dsc/local_configuration_manager.rb | 32 | ||||
-rw-r--r-- | spec/unit/util/dsc/lcm_output_parser_spec.rb | 120 | ||||
-rw-r--r-- | spec/unit/util/dsc/local_configuration_manager_spec.rb | 3 |
5 files changed, 182 insertions, 38 deletions
diff --git a/lib/chef/provider/dsc_script.rb b/lib/chef/provider/dsc_script.rb index 66783ceb0f..226e6a8234 100644 --- a/lib/chef/provider/dsc_script.rb +++ b/lib/chef/provider/dsc_script.rb @@ -165,7 +165,11 @@ class Chef if resource.changes_state? # We ignore the last log message because it only contains the time it took, which looks weird cleaned_messages = resource.change_log[0..-2].map { |c| c.sub(/^#{Regexp.escape(resource.name)}/, "").strip } - "converge DSC resource #{resource.name} by #{cleaned_messages.find_all { |c| c != '' }.join("\n")}" + unless cleaned_messages.empty? + "converge DSC resource #{resource.name} by #{cleaned_messages.find_all { |c| c != '' }.join("\n")}" + else + "converge DSC resource #{resource.name}" + end else # This is needed because a dsc script can have resources that are both converged and not "converge DSC resource #{resource.name} by doing nothing because it is already converged" diff --git a/lib/chef/util/dsc/lcm_output_parser.rb b/lib/chef/util/dsc/lcm_output_parser.rb index 9473ca8a86..f19f637b6d 100644 --- a/lib/chef/util/dsc/lcm_output_parser.rb +++ b/lib/chef/util/dsc/lcm_output_parser.rb @@ -28,7 +28,7 @@ class Chef # Parses the output from LCM and returns a list of Chef::Util::DSC::ResourceInfo objects # that describe how the resources affected the system # - # Example: + # Example for WhatIfParser: # parse <<-EOF # What if: [Machine]: LCM: [Start Set ] # What if: [Machine]: LCM: [Start Resource ] [[File]FileToNotBeThere] @@ -53,7 +53,62 @@ class Chef # ) # ] # - def self.parse(lcm_output) + # Example for TestDSCParser: + # parse <<-EOF + # InDesiredState : False + # ResourcesInDesiredState : + # ResourcesNotInDesiredState: {[Environment]texteditor} + # ReturnValue : 0 + # PSComputerName : . + # EOF + # + # would return + # + # [ + # Chef::Util::DSC::ResourceInfo.new( + # '{[Environment]texteditor}', + # true, + # [ + # ] + # ) + # ] + # + + def self.parse(lcm_output, test_dsc_configuration) + 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 = Hash.new + + resources = [] + lcm_output.lines.each do |line| + op_action , op_value = line.strip.split(":") + op_action&.strip! + case op_action + when "InDesiredState" + current_resource[:skipped] = op_value.strip == "True" ? true : false + when "ResourcesInDesiredState" + current_resource[:name] = op_value.strip if op_value + when "ResourcesNotInDesiredState" + current_resource[:name] = op_value.strip if op_value + when "ReturnValue" + current_resource[:context] = nil + end + end + if current_resource[:name] + resources.push(current_resource) + end + + if resources.length > 0 + build_resource_info(resources) + else + raise Chef::Exceptions::LCMParser, "Could not parse:\n#{lcm_output}" + end + end + + def self.what_if_parser(lcm_output) lcm_output ||= "" current_resource = Hash.new diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb index 1f154b1c71..07109f7f92 100644 --- a/lib/chef/util/dsc/local_configuration_manager.rb +++ b/lib/chef/util/dsc/local_configuration_manager.rb @@ -29,7 +29,7 @@ class Chef::Util::DSC def test_configuration(configuration_document, shellout_flags) status = run_configuration_cmdlet(configuration_document, false, shellout_flags) - log_what_if_exception(status.stderr) unless status.succeeded? + log_dsc_exception(status.stderr) unless status.succeeded? configuration_update_required?(status.return_value) end @@ -77,7 +77,7 @@ class Chef::Util::DSC ps4_base_command else if ps_version_gte_5? - "#{common_command_prefix} Test-DscConfiguration -path #{@configuration_path}" + "#{common_command_prefix} Test-DscConfiguration -path #{@configuration_path} | format-list" else ps4_base_command + " -whatif; if (! $?) { exit 1 }" end @@ -88,31 +88,31 @@ class Chef::Util::DSC Chef::Platform.supported_powershell_version?(@node, 5) end - def log_what_if_exception(what_if_exception_output) - if whatif_not_supported?(what_if_exception_output) + def log_dsc_exception(dsc_exception_output) + if whatif_not_supported?(dsc_exception_output) # LCM returns an error if any of the resources do not support the opptional What-If Chef::Log.warn("Received error while testing configuration due to resource not supporting 'WhatIf'") - elsif dsc_module_import_failure?(what_if_exception_output) - Chef::Log.warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") + elsif dsc_module_import_failure?(dsc_exception_output) + Chef::Log.warn("Received error while testing configuration due to a module for an imported resource possibly not being fully installed:\n#{dsc_exception_output.gsub(/\s+/, ' ')}") else - Chef::Log.warn("Received error while testing configuration:\n#{what_if_exception_output.gsub(/\s+/, ' ')}") + Chef::Log.warn("Received error while testing configuration:\n#{dsc_exception_output.gsub(/\s+/, ' ')}") end end - def whatif_not_supported?(what_if_exception_output) - !! (what_if_exception_output.gsub(/[\r\n]+/, "").gsub(/\s+/, " ") =~ /A parameter cannot be found that matches parameter name 'Whatif'/i) + 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) end - def dsc_module_import_failure?(what_if_output) - !! (what_if_output =~ /\sCimException/ && - what_if_output =~ /ProviderOperationExecutionFailure/ && - what_if_output =~ /\smodule\s+is\s+installed/) + def dsc_module_import_failure?(command_output) + !! (command_output =~ /\sCimException/ && + command_output =~ /ProviderOperationExecutionFailure/ && + command_output =~ /\smodule\s+is\s+installed/) end - def configuration_update_required?(what_if_output) - Chef::Log.debug("DSC: DSC returned the following '-whatif' output from test operation:\n#{what_if_output}") + def configuration_update_required?(command_output) + Chef::Log.debug("DSC: DSC returned the following '-whatif' output from test operation:\n#{command_output}") begin - Parser.parse(what_if_output) + Parser.parse(command_output, ps_version_gte_5?) rescue Chef::Exceptions::LCMParser => e Chef::Log.warn("Could not parse LCM output: #{e}") [Chef::Util::DSC::ResourceInfo.new("Unknown DSC Resources", true, ["Unknown changes because LCM output was not parsable."])] diff --git a/spec/unit/util/dsc/lcm_output_parser_spec.rb b/spec/unit/util/dsc/lcm_output_parser_spec.rb index d59497de6f..65eaafe19c 100644 --- a/spec/unit/util/dsc/lcm_output_parser_spec.rb +++ b/spec/unit/util/dsc/lcm_output_parser_spec.rb @@ -19,20 +19,33 @@ require "chef/util/dsc/lcm_output_parser" describe Chef::Util::DSC::LocalConfigurationManager::Parser do - context "empty input parameter" do + context "empty input parameter for WhatIfParser" do it "raises an exception when there are no valid lines" do str = <<-EOF EOF - expect { Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) }.to raise_error(Chef::Exceptions::LCMParser) + expect { Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, false) }.to raise_error(Chef::Exceptions::LCMParser) end it "raises an exception for a nil input" do - expect { Chef::Util::DSC::LocalConfigurationManager::Parser.parse(nil) }.to raise_error(Chef::Exceptions::LCMParser) + expect { Chef::Util::DSC::LocalConfigurationManager::Parser.parse(nil, false) }.to raise_error(Chef::Exceptions::LCMParser) end end - context "correctly formatted output from lcm" do + context "empty input parameter for TestDSCParser" do + it "raises an exception when there are no valid lines" do + str = <<-EOF + + EOF + expect { Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, true) }.to raise_error(Chef::Exceptions::LCMParser) + end + + it "raises an exception for a nil input" do + expect { Chef::Util::DSC::LocalConfigurationManager::Parser.parse(nil, true) }.to raise_error(Chef::Exceptions::LCMParser) + end + end + + context "correctly formatted output from lcm for WhatIfParser" do it "returns a single resource when only 1 logged with the correct name" do str = <<EOF logtype: [machinename]: LCM: [ Start Set ] @@ -40,7 +53,7 @@ logtype: [machinename]: LCM: [ Start Resource ] [name] logtype: [machinename]: LCM: [ End Resource ] [name] logtype: [machinename]: LCM: [ End Set ] EOF - resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, false) expect(resources.length).to eq(1) expect(resources[0].name).to eq("[name]") end @@ -54,7 +67,7 @@ logtype: [machinename]: LCM: [ End Set ] [name] logtype: [machinename]: LCM: [ End Resource ] [name] logtype: [machinename]: LCM: [ End Set ] EOF - resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, false) expect(resources[0].changes_state?).to be_truthy end @@ -68,11 +81,11 @@ logtype: [machinename]: LCM: [ End Set ] [name] logtype: [machinename]: LCM: [ End Resource ] [name] logtype: [machinename]: LCM: [ End Set ] EOF - resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, false) expect(resources[0].change_log).to match_array(["[name]", "[message]", "[name]"]) end - it "should return false for changes_state?" do + it "returns false for changes_state?" do str = <<EOF logtype: [machinename]: LCM: [ Start Set ] logtype: [machinename]: LCM: [ Start Resource ] [name] @@ -80,11 +93,11 @@ logtype: [machinename]: LCM: [ Skip Set ] [name] logtype: [machinename]: LCM: [ End Resource ] [name] logtype: [machinename]: LCM: [ End Set ] EOF - resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, false) expect(resources[0].changes_state?).to be_falsey end - it "should return an empty array for change_log if changes_state? is false" do + it "returns an empty array for change_log if changes_state? is false" do str = <<EOF logtype: [machinename]: LCM: [ Start Set ] logtype: [machinename]: LCM: [ Start Resource ] [name] @@ -92,13 +105,64 @@ logtype: [machinename]: LCM: [ Skip Set ] [name] logtype: [machinename]: LCM: [ End Resource ] [name] logtype: [machinename]: LCM: [ End Set ] EOF - resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, false) expect(resources[0].change_log).to be_empty end end - context "Incorrectly formatted output from LCM" do - it "should allow missing a [End Resource] when its the last one and still find all the resource" do + context "correctly formatted output from lcm for TestDSCParser" do + it "returns a single resource when only 1 logged with the correct name" do + str = <<EOF +InDesiredState : False +ResourcesInDesiredState : +ResourcesNotInDesiredState: [name] +ReturnValue : 0 +PSComputerName : . +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, true) + expect(resources.length).to eq(1) + expect(resources[0].name).to eq("[name]") + end + + it "identifies when a resource changes the state of the system" do + str = <<EOF +InDesiredState : False +ResourcesInDesiredState : +ResourcesNotInDesiredState: [name] +ReturnValue : 0 +PSComputerName : . +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, true) + expect(resources[0].changes_state?).to be_truthy + end + + it "returns false for changes_state?" do + str = <<EOF +InDesiredState : True +ResourcesInDesiredState : [name] +ResourcesNotInDesiredState: +ReturnValue : 0 +PSComputerName : . +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, true) + expect(resources[0].changes_state?).to be_falsey + end + + it "returns an empty array for change_log if changes_state? is false" do + str = <<EOF +InDesiredState : True +ResourcesInDesiredState : [name] +ResourcesNotInDesiredState: +ReturnValue : 0 +PSComputerName : . +EOF + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, true) + expect(resources[0].change_log).to be_empty + end + end + + context "Incorrectly formatted output from LCM for WhatIfParser" do + it "allows missing [End Resource] when its the last one and still find all the resource" do str = <<-EOF logtype: [machinename]: LCM: [ Start Set ] logtype: [machinename]: LCM: [ Start Resource ] [name] @@ -114,12 +178,12 @@ logtype: [machinename]: LCM: [ End Set ] logtype: [machinename]: LCM: [ End Set ] EOF - resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, false) expect(resources[0].changes_state?).to be_falsey expect(resources[1].changes_state?).to be_truthy end - it "should allow missing a [End Resource] when its the first one and still find all the resource" do + it "allow missing [End Resource] when its the first one and still find all the resource" do str = <<-EOF logtype: [machinename]: LCM: [ Start Set ] logtype: [machinename]: LCM: [ Start Resource ] [name] @@ -135,12 +199,12 @@ logtype: [machinename]: LCM: [ End Resource ] logtype: [machinename]: LCM: [ End Set ] EOF - resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, false) expect(resources[0].changes_state?).to be_falsey expect(resources[1].changes_state?).to be_truthy end - it "should allow missing set and end resource and assume an unconverged resource in this case" do + it "allows missing set and end resource and assume an unconverged resource in this case" do str = <<-EOF logtype: [machinename]: LCM: [ Start Set ] logtype: [machinename]: LCM: [ Start Resource ] [name] @@ -154,11 +218,31 @@ logtype: [machinename]: LCM: [ End Set ] logtype: [machinename]: LCM: [ End Resource ] logtype: [machinename]: LCM: [ End Set ] EOF - resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str) + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, false) expect(resources[0].changes_state?).to be_truthy expect(resources[0].name).to eql("[name]") expect(resources[1].changes_state?).to be_truthy expect(resources[1].name).to eql("[name2]") end end + + context "Incorrectly formatted output from LCM for TestDSCParser" do + it "allows missing [End Resource] when its the last one and still find all the resource" do + str = <<EOF +InDesiredState : True +ResourcesInDesiredState : +ResourcesNotInDesiredState: [name] +ReturnValue : 0 +PSComputerName : . +InDesiredState : True +ResourcesInDesiredState : +ResourcesNotInDesiredState: [name2] +ReturnValue : 0 +PSComputerName : . +EOF + + resources = Chef::Util::DSC::LocalConfigurationManager::Parser.parse(str, true) + expect(resources[0].changes_state?).to be_falsey + end + end end diff --git a/spec/unit/util/dsc/local_configuration_manager_spec.rb b/spec/unit/util/dsc/local_configuration_manager_spec.rb index c87b446286..4c33fc8616 100644 --- a/spec/unit/util/dsc/local_configuration_manager_spec.rb +++ b/spec/unit/util/dsc/local_configuration_manager_spec.rb @@ -57,6 +57,7 @@ EOH 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(:ps_version_gte_5?).and_return(false) end context "that returns successfully" do let(:lcm_standard_output) { normal_lcm_output } @@ -103,7 +104,7 @@ EOH 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" } + 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 } |