summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpiyushawasthi <piyush.awasthi@msystechnologies.com>2017-10-04 20:34:04 +0530
committerBryan McLellan <btm@loftninjas.org>2017-10-16 21:38:49 -0400
commit4a080d5737912076d97632937d5b5126edc33c71 (patch)
tree9bb93cebcfe122c39f344044cbfa4a743c99c8a0
parent81ca24afdef21b96f7fe349f08ffde00e090bf94 (diff)
downloadchef-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.rb6
-rw-r--r--lib/chef/util/dsc/lcm_output_parser.rb59
-rw-r--r--lib/chef/util/dsc/local_configuration_manager.rb32
-rw-r--r--spec/unit/util/dsc/lcm_output_parser_spec.rb120
-rw-r--r--spec/unit/util/dsc/local_configuration_manager_spec.rb3
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 }