# # Author:: Jay Mundrawala () # # 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 "../../log" require_relative "resource_info" require_relative "../../exceptions" class Chef class Util class DSC class LocalConfigurationManager module Parser # Parses the output from LCM and returns a list of Chef::Util::DSC::ResourceInfo objects # that describe how the resources affected the system # # Example for WhatIfParser: # 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' # ] # ) # ] # # 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 = {} 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 = {} resources = [] lcm_output.lines.each do |line| op_action, op_type, info = parse_line(line) case op_action when :start case op_type when :set if current_resource[:name] current_resource[:context] = :logging current_resource[:logs] = [info] end when :resource if current_resource[:name] resources.push(current_resource) end current_resource = { name: info } else Chef::Log.trace("Ignoring op_action #{op_action}: Read line #{line}") end when :end # Make sure we log the last line if current_resource[:context] == :logging && info.include?(current_resource[:name]) current_resource[:logs].push(info) end current_resource[:context] = nil when :skip current_resource[:skipped] = true when :info if current_resource[:context] == :logging current_resource[:logs].push(info) end 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.parse_line(line) if match = line.match(/^.*?:.*?:\s*LCM:\s*\[(.*?)\](.*)/) # If the line looks like # What If: [machinename]: 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 op_action = op_type = :info if match = line.match(/^.*?:.*?: \s+(.*)/) info = match.captures[0] else info = line end end info.strip! # Because this was formatted for humans [op_action, op_type, info] end private_class_method :parse_line def self.build_resource_info(resources) resources.map do |r| Chef::Util::DSC::ResourceInfo.new(r[:name], !r[:skipped], r[:logs]) end end private_class_method :build_resource_info end end end end end