diff options
author | Jay Mundrawala <jdmundrawala@gmail.com> | 2014-08-13 12:39:01 -0700 |
---|---|---|
committer | Jay Mundrawala <jdmundrawala@gmail.com> | 2014-09-19 12:47:30 -0700 |
commit | 5f91bff0d9ca8c745f47f950860d8d7663aede42 (patch) | |
tree | 5a0ecf51e509424937d4673c59e2181e41fec50c /lib/chef/util/dsc | |
parent | c905b5521d3a37b76d3ab294f7bd786e9f390258 (diff) | |
download | chef-5f91bff0d9ca8c745f47f950860d8d7663aede42.tar.gz |
Parse WhatIf from LCM
Diffstat (limited to 'lib/chef/util/dsc')
-rw-r--r-- | lib/chef/util/dsc/lcm_output_parser.rb | 172 | ||||
-rw-r--r-- | lib/chef/util/dsc/local_configuration_manager.rb | 26 | ||||
-rw-r--r-- | lib/chef/util/dsc/resource_info.rb | 26 |
3 files changed, 201 insertions, 23 deletions
diff --git a/lib/chef/util/dsc/lcm_output_parser.rb b/lib/chef/util/dsc/lcm_output_parser.rb new file mode 100644 index 0000000000..420901bcfa --- /dev/null +++ b/lib/chef/util/dsc/lcm_output_parser.rb @@ -0,0 +1,172 @@ +# +# Author:: Jay Mundrawala (<jdm@getchef.com>) +# +# Copyright:: 2014, 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/util/dsc/resource_info' + +class Chef + class Util + class DSC + class LocalConfigurationManager + module Parser + class ParseException < RuntimeError; end + + class Operation + attr_reader :op_type + attr_reader :resources + attr_reader :info + attr_reader :sets + attr_reader :tests + + def initialize(op_type, info) + @op_type = op_type + @info = [] + @sets = [] + @tests = [] + @resources = [] + add_info(info) + end + + def add_info(info) + @info << info + end + + def add_set(set) + raise ParseException, "add_set is not allowed in this context. Found #{@op_type}" unless [:resource, :set].include?(@op_type) + @sets << set + end + + def add_test(test) + raise ParseException, "add_test is not allowed in this context. Found #{@op_type}" unless [:resource, :set].include?(@op_type) + @tests << test + end + + def add_resource(resource) + raise ParseException, 'add_resource is only allowed to be added to the set op_type' unless @op_type == :set + @resources << resource + end + end + + # Parses the output from LCM and returns a list of Chef::Util::DSC::ResourceInfo objects + # that describe how the resources affected the system + # + # Example: + # parse <<-EOF + # What if: [Machine]: LCM: [Start Set ] + # What if: [Machine]: LCM: [Start Resource ] [[File]FileToNotBeThere] + # What if: [Machine]: LCM: [Start Set ] [[File]FileToNotBeThere] + # What if: [C:\ShouldNotExist.txt] removed + # What if: [Machine]: LCM: [End Set ] [[File]FileToNotBeThere] in 0.1 seconds + # What if: [Machine]: LCM: [End Resource ] [[File]FileToNotBeThere] + # What if: [Machine]: LCM: [End Set ] + # EOF + # + # would return + # + # [ + # Chef::Util::DSC::ResourceInfo.new( + # '[[File]FileToNotBeThere]', + # true, + # [ + # '[[File]FileToNotBeThere]', + # '[C:\Shouldnotexist.txt]', + # '[[File]FileToNotBeThere] in 0.1 seconds' + # ] + # ) + # ] + # + def self.parse(lcm_output) + return [] unless lcm_output + + stack = Array.new + popped_op = nil + lcm_output.lines.each do |line| + op_action, op_type, info = parse_line(line) + info.strip! # Because this was formatted for humans + + # The rules: + # - For each `start` action, there must be a matching `end` action + # - `skip` actions do not not do anything (They don't add to the stack) + case op_action + when :start + new_op = Operation.new(op_type, info) + case op_type + when :set + stack[-1].add_set(new_op) if stack[-1] + when :test + stack[-1].add_test(new_op) + when :resource + stack[-1].add_resource(new_op) + else + Chef::Log.warn("Unknown op_action #{op_action}: Read line #{line}") + end + stack.push(new_op) + when :end + popped_op = stack.pop + popped_op.add_info(info) + if popped_op.op_type != op_type + raise LCMOutputParseException, "Unmatching end for op_type. Expected op_type=#{op_type}, found op_type=#{popped_op.op_type}" + end + when :skip + # We don't really have anything to do here + when :info + stack[-1].add_info(info) if stack[-1] + else + stack[-1].add_info(line) if stack[-1] + end + end + + op_to_resource_infos(popped_op) + end + + def self.parse_line(line) + if match = line.match(/^.*?:.*?:\s*LCM:\s*\[(.*?)\](.*)/) + # If the line looks like + # x: [y]: LCM: [op_action op_type] message + # extract op_action, op_type, and message + operation, info = match.captures + op_action, op_type = operation.strip.split(' ').map {|m| m.downcase.to_sym} + else + # If the line looks like + # x: [y]: message + # extract message + match = line.match(/^.*?:.*?: \s+(.*)/) + op_action = op_type = :info + info = match.captures[0] + end + info.strip! # Because this was formatted for humans + return [op_action, op_type, info] + end + private_class_method :parse_line + + def self.op_to_resource_infos(op) + resources = op ? op.resources : [] + + resources.map do |r| + name = r.info[0] + sets = r.sets.length > 0 + change_log = r.sets[-1].info if sets + Chef::Util::DSC::ResourceInfo.new(name, sets, change_log) + end + end + private_class_method :op_to_resource_infos + + end + end + end + end +end diff --git a/lib/chef/util/dsc/local_configuration_manager.rb b/lib/chef/util/dsc/local_configuration_manager.rb index a9c5b867df..d7d532a686 100644 --- a/lib/chef/util/dsc/local_configuration_manager.rb +++ b/lib/chef/util/dsc/local_configuration_manager.rb @@ -17,6 +17,7 @@ # require 'chef/util/powershell/cmdlet' +require 'chef/util/dsc/lcm_output_parser' class Chef::Util::DSC class LocalConfigurationManager @@ -68,7 +69,8 @@ class Chef::Util::DSC def configuration_update_required?(what_if_output) Chef::Log.debug("DSC: DSC returned the following '-whatif' output from test operation:\n#{what_if_output}") - parse_what_if_output(what_if_output) + #parse_what_if_output(what_if_output) + Parser::parse(what_if_output) end def save_configuration_document(configuration_document) @@ -86,28 +88,6 @@ class Chef::Util::DSC File.join(@configuration_path,'..mof') end - def parse_what_if_output(what_if_output) - - # What-if output for start-dscconfiguration contains lines that look like one of the following: - # - # What if: [SEA-ADAMED1]: LCM: [ Start Set ] [[Group]chef_dsc] - # What if: [SEA-ADAMED1]: [[Group]chef_dsc] Performing the operation "Add" on target "Group: demo1" - # - # The second line lacking the 'LCM:' is what happens if there is a change required to make the system consistent with the resource. - # Such a line without LCM is only present if an update to the system is required. Therefore, we test each line below - # to see if it is missing the LCM, and declare that an update is needed if so. - has_change_line = false - - what_if_output.lines.each do |line| - if (line =~ /.+\:\s+\[\S*\]\:\s+LCM\:/).nil? - has_change_line = true - break - end - end - - has_change_line - end - def clear_execution_time @operation_start_time = nil @operation_end_time = nil diff --git a/lib/chef/util/dsc/resource_info.rb b/lib/chef/util/dsc/resource_info.rb new file mode 100644 index 0000000000..4a32451721 --- /dev/null +++ b/lib/chef/util/dsc/resource_info.rb @@ -0,0 +1,26 @@ + +class Chef + class Util + class DSC + class ResourceInfo + # The name is the text following [Start Set] + attr_reader :name + + # A list of all log messages between [Start Set] and [End Set]. + # Each line is an element in the list. + attr_reader :change_log + + def initialize(name, sets, change_log) + @name = name + @sets = sets + @change_log = change_log || [] + end + + # Does this resource change the state of the system? + def changes_state? + @sets + end + end + end + end +end |