1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
#
# 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/log'
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
while stack[-1].op_type != :set
Chef::Log.warn("Can't add resource to set...popping until it is allowed.")
popped_op = stack.pop
end
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)
while popped_op.op_type != op_type
Chef::Log::warn("Unmatching end for op_type. Expected op_type=#{op_type}, found op_type=#{popped_op.op_type}. From output:\n#{lcm_output}")
popped_op = stack.pop
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
|