forked from ManageIQ/manageiq
-
Notifications
You must be signed in to change notification settings - Fork 0
/
condition.rb
298 lines (247 loc) · 9.91 KB
/
condition.rb
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
class Condition < ApplicationRecord
include UuidMixin
include ReadOnlyMixin
before_validation :default_name_to_guid, :on => :create
validates :name, :description, :expression, :towhat, :presence => true
validates :name, :description, :uniqueness_when_changed => true
acts_as_miq_taggable
acts_as_miq_set_member
belongs_to :miq_policy
has_and_belongs_to_many :miq_policies
serialize :expression
serialize :applies_to_exp
attr_accessor :reserved
TOWHAT_APPLIES_TO_CLASSES = {
"ContainerGroup" => ui_lookup(:model => "ContainerGroup"),
"ContainerImage" => ui_lookup(:model => "ContainerImage"),
"ContainerNode" => ui_lookup(:model => "ContainerNode"),
"ContainerProject" => ui_lookup(:model => "ContainerProject"),
"ContainerReplicator" => ui_lookup(:model => "ContainerReplicator"),
"ExtManagementSystem" => ui_lookup(:model => "ExtManagementSystem"),
"Host" => ui_lookup(:model => "Host"),
"PhysicalServer" => ui_lookup(:model => "PhysicalServer"),
"Vm" => ui_lookup(:model => "Vm")
}.freeze
def applies_to?(rec, inputs = {})
rec_model = rec.class.base_model.name
rec_model = "Vm" if rec_model.downcase.match?("template")
return false if towhat && rec_model != towhat
return true if applies_to_exp.nil?
Condition.evaluate(self, rec, inputs, :applies_to_exp)
end
def self.conditions
pluck(:expression)
end
def self.evaluate(cond, rec, _inputs = {}, attr = :expression)
expression = cond.send(attr)
name = cond.try(:description) || cond.try(:name)
mode = expression.kind_of?(MiqExpression) ? "object" : expression["mode"]
case mode
when "tag"
unless %w[any all none].include?(expression["include"])
raise _("condition '%{name}', include value \"%{value}\", is invalid. Should be one of \"any, all or none\"") %
{:name => name, :value => expression["include"]}
end
result = expression["include"] != "any"
expression["tag"].split.each do |tag|
if rec.is_tagged_with?(tag, :ns => expression["ns"])
result = true if expression["include"] == "any"
result = false if expression["include"] == "none"
else
result = false if expression["include"] == "all"
end
end
when "tag_expr", "tag_expr_v2", "object"
expr = case mode
when "tag_expr"
expression["expr"]
when "tag_expr_v2"
MiqExpression.new(expression["expr"]).to_ruby
when "object"
expression.to_ruby
end
result = subst_matches?(expr, rec)
end
result
end
# similar to MiqExpression#evaluate
# @return [Boolean] true if the expression matches the record
def self.subst_matches?(expr, rec)
do_eval(subst(expr, rec), rec)
end
def self.do_eval(expr, rec)
!!eval(expr, binding)
end
private_class_method :do_eval
def self.subst(expr, rec)
findexp = /<find>(.+?)<\/find>/im
if expr =~ findexp
expr = expr.gsub!(findexp) { |_s| _subst_find(rec, $1.strip) }
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Find Expression after substitution: [#{expr}]")
end
# <mode>/virtual/operating_system/product_name</mode>
# <mode WE/JWSref=host>/managed/environment/prod</mode>
expr.gsub!(/<(value|exist|count|registry)([^>]*)>([^<]+)<\/(value|exist|count|registry)>/im) { |_s| _subst(rec, $2.strip, $3.strip, $1.strip) }
# <mode /virtual/operating_system/product_name />
expr.gsub!(/<(value|exist|count|registry)([^>]+)\/>/im) { |_s| _subst(rec, nil, $2.strip, $1.strip) }
expr
end
private_class_method :subst
def self._subst(rec, opts, tag, mode)
ohash, ref = options2hash(opts, rec)
case mode.downcase
when "exist"
ref.nil? ? value = false : value = ref.is_tagged_with?(tag, :ns => "*")
when "value"
if ref.kind_of?(Hash)
value = ref.fetch(tag, "")
else
value = ref.nil? ? "" : Tag.list(ref, :ns => tag)
end
value = MiqExpression.quote(value, ohash[:type]&.to_sym)
when "count"
ref.nil? ? value = 0 : value = ref.tag_list(:ns => tag).length
when "registry"
ref.nil? ? value = "" : value = registry_data(ref, tag, ohash)
value = MiqExpression.quote(value, ohash[:type]&.to_sym)
end
value
end
private_class_method :_subst
def self.collect_children(ref, methods)
method = methods.shift
list = ref.send(method)
return [] if list.nil?
result = methods.empty? ? Array(list) : []
Array(list).each do |obj|
result.concat(collect_children(obj, methods)) unless methods.empty?
end
result
end
private_class_method :collect_children
def self._subst_find(rec, expr)
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Find Expression before substitution: [#{expr}]")
searchexp = /<search>(.+)<\/search>/im
expr =~ searchexp
search = $1
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Search Expression before substitution: [#{search}]")
listexp = /<value([^>]*)>(.+)<\/value>/im
search =~ listexp
opts, _ref = options2hash($1, rec)
methods = $2.split("/")
methods.shift
methods.shift
attr = methods.pop
l = collect_children(rec, methods)
return false if l.empty?
list = l.collect do |obj|
value = MiqExpression.quote(obj.send(attr), opts[:type]&.to_sym)
value = value.gsub("\\", '\&\&') if value.kind_of?(String)
e = search.gsub(/<value[^>]*>.+<\/value>/im, value.to_s)
obj if do_eval(e, obj)
end.compact
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Search Expression returned: [#{list.length}] records")
checkexp = /<check([^>]*)>(.+)<\/check>/im
expr =~ checkexp
checkopts = $1.strip
check = $2
checkmode = checkopts.split("=").last.strip.downcase
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Check Expression before substitution: [#{check}], options: [#{checkopts}]")
if checkmode == "count"
e = check.gsub(/<count>/i, list.length.to_s)
left, operator, right = e.split
raise _("Illegal operator, '%{operator}'") % {:operator => operator} unless %w[== != < > <= >=].include?(operator)
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Check Expression after substitution: [#{e}]")
result = !!left.to_f.send(operator, right.to_f)
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Check Expression result: [#{result}]")
return result
end
return false if list.empty?
check =~ /<value([^>]*)>(.+)<\/value>/im
raw_opts = $1
tag = $2
checkattr = tag.split("/").last.strip
result = true
list.each do |obj|
opts, _ref = options2hash(raw_opts, obj)
value = MiqExpression.quote(obj.send(checkattr), opts[:type]&.to_sym)
value = value.gsub("\\", '\&\&') if value.kind_of?(String)
e = check.gsub(/<value[^>]*>.+<\/value>/im, value.to_s)
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Check Expression after substitution: [#{e}]")
result = do_eval(e, obj)
return true if result && checkmode == "any"
return false if !result && checkmode == "all"
end
MiqPolicy.logger.debug("MIQ(condition-_subst_find): Check Expression result: [#{result}]")
result
end
def self.options2hash(opts, rec)
ref = rec
ohash = {}
if opts.present?
val = nil
opts.split(",").each do |o|
attr, val = o.split("=")
ohash[attr.strip.downcase.to_sym] = val.strip.downcase
end
if ohash[:ref] != rec.class.to_s.downcase && !exclude_from_object_ref_substitution(ohash[:ref], rec)
ref = rec.send(val) if val && rec.respond_to?(val)
end
end
return ohash, ref
end
def self.exclude_from_object_ref_substitution(reference, rec)
case reference
when "service"
rec.kind_of?(Service)
end
end
def self.registry_data(ref, name, ohash)
# <registry>HKLM\Software\Microsoft\Windows\CurrentVersion\explorer\Shell Folders\Common AppData</registry> == 'C:\Documents and Settings\All Users\Application Data'
# <registry>HKLM\Software\Microsoft\Windows\CurrentVersion\explorer\Shell Folders : Common AppData</registry> == 'C:\Documents and Settings\All Users\Application Data'
return nil unless ref.respond_to?(:registry_items)
registry_items = ref.registry_items
if ohash[:key_exists]
registry_items.where("name LIKE ? ESCAPE ''", name + "%").exists?
elsif ohash[:value_exists]
registry_items.where(:name => name).exists?
else
registry_items.find_by(:name => name)&.data
end
end
def export_to_array
h = attributes
["id", "created_on", "updated_on"].each { |k| h.delete(k) }
[self.class.to_s => h]
end
def self.import_from_hash(condition, options = {})
# To delete condition modifier in policy from versions 5.8 and older
condition["expression"].exp = {"not" => condition["expression"].exp} if condition["modifier"] == 'deny'
condition.delete("modifier")
status = {:class => name, :description => condition["description"]}
c = Condition.find_by(:guid => condition["guid"]) || Condition.find_by(:name => condition["name"]) ||
Condition.find_by(:description => condition["description"])
msg_pfx = "Importing Condition: guid=[#{condition["guid"]}] description=[#{condition["description"]}]"
if c.nil?
c = Condition.new(condition)
status[:status] = :add
else
status[:old_description] = c.description
c.attributes = condition
status[:status] = :update
end
unless c.valid?
status[:status] = :conflict
status[:messages] = c.errors.full_messages
end
msg = "#{msg_pfx}, Status: #{status[:status]}"
msg += ", Messages: #{status[:messages].join(",")}" if status[:messages]
if options[:preview] == true
MiqPolicy.logger.info("[PREVIEW] #{msg}")
else
MiqPolicy.logger.info(msg)
c.save!
end
return c, status
end
end # class Condition