forked from ManageIQ/manageiq
-
Notifications
You must be signed in to change notification settings - Fork 1
/
assignment_mixin.rb
270 lines (231 loc) · 10.2 KB
/
assignment_mixin.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
# Generic mixin module that supports assignment by CI of management tags
module AssignmentMixin
extend ActiveSupport::Concern
ESCAPED_PREFIX = "escaped".freeze
NAMESPACE_SUFFIX = "assigned_to".freeze
def all_assignments(tag = nil)
scope = Tag.where(["name LIKE ?", "%/#{AssignmentMixin::NAMESPACE_SUFFIX}/%"])
scope = scope.where(["name LIKE ?", "%#{tag}"]) if tag.present?
scope
end
module_function :all_assignments
included do # :nodoc:
acts_as_miq_taggable
const_set(:ASSIGNMENT_PARENT_ASSOCIATIONS, %i[parent_blue_folders parent_resource_pool host ems_cluster ext_management_system my_enterprise physical_server]) unless const_defined?(:ASSIGNMENT_PARENT_ASSOCIATIONS)
cache_with_timeout(:assignments_cached, 1.minute) { assignments }
end
def assign_to_objects(objects, klass = nil)
# objects => A single item or array of items
# item => A CI instance (not classification) or a CI id of klass
# klass => The class of the object that self is to be assigned to - (Takes both forms - Host or host, EmsCluster or ems_cluster)
Array.wrap(objects).each do |obj|
tag = build_object_tag_path(obj, klass)
tag_add(tag, :ns => namespace)
end
reload
end
def unassign_objects(objects, klass = nil)
# objects => A single item or array of items
# item => A CI instance (not classification) or a CI id of klass
# klass => The class of the object that self is to be unassigned from - (Takes both forms - Host or host, EmsCluster or ems_cluster)
Array.wrap(objects).each do |obj|
tag = build_object_tag_path(obj, klass)
tag_remove(tag, :ns => namespace)
end
reload
end
def assign_to_tags(objects, klass)
# objects => A single item or array of items
# item => A classification entry instance or a classification entry id
# klass => The class of the object that self is to be assigned to - (Takes both forms - Host or host, EmsCluster or ems_cluster)
Array.wrap(objects).each do |obj|
tag = build_tag_tagging_path(obj, klass)
next if tag.nil?
tag_add(tag, :ns => namespace)
end
reload
end
def unassign_tags(objects, klass)
Array.wrap(objects).each do |obj|
tag = build_tag_tagging_path(obj, klass)
next if tag.nil?
tag_remove(tag, :ns => namespace)
end
reload
end
def assign_to_labels(objects, klass)
# objects => A single item or array of items
# item => A classification entry instance or a classification entry id
# klass => The class of the object that self is to be assigned to - (Takes both forms - Host or host, EmsCluster or ems_cluster)
Array.wrap(objects).each do |obj|
unless obj.kind_of?(ActiveRecord::Base) # obj is the id of a classification entry instance
id = obj
obj = CustomAttribute.find_by(:id => id)
if obj.nil?
_log.warn("Unable to find label with id [#{id}], skipping assignment")
next
end
end
tag = build_label_tag_path(obj, klass)
tag_add(tag, :ns => namespace)
end
reload
end
def unassign_labels(objects, klass)
Array.wrap(objects).each do |obj|
tag = build_label_tag_path(obj, klass)
next if tag.nil?
tag_remove(tag, :ns => namespace)
end
reload
end
def get_assigned_tos
# Returns: {:objects => [obj, obj, ...], :tags => [[Classification.entry_object, klass], ...]}
result = {:objects => [], :tags => [], :labels => []}
tags = tag_list(:ns => namespace).split
tags.each do |t|
parts = t.split("/")
klass = parts.shift
type = parts.shift
case type.to_sym
when :id
model = Object.const_get(klass.camelize) rescue nil
object = model.find_by(:id => parts.pop) unless model.nil?
result[:objects] << object unless object.nil?
when :tag
tag = Tag.find_by(:name => "/" + parts.join("/"))
classification = Classification.find_by(:tag_id => tag.id) if tag
result[:tags] << [classification, klass] if classification
when :label
label = if AssignmentMixin.escaped?(parts[1])
name = AssignmentMixin.unescape(parts[1])
value = AssignmentMixin.unescape(parts[2])
CustomAttribute.find_by(:name => name, :value => value)
else
CustomAttribute.find_by(:name => parts[1], :value => parts[2])
end
result[:labels] << [label, klass] unless label.nil?
end
end
result
end
# make strings with special characters like '/' safe to put in tags(assignments) by escaping them
def self.escape(string)
@parser ||= URI::RFC2396_Parser.new
escaped_string = @parser.escape(string, /[^A-Za-z0-9]/)
"#{ESCAPED_PREFIX}:{#{escaped_string}}" # '/escape/string' --> 'escaped:{%2Fescape%2Fstring}'
end
# return the escaped string back into a normal string
def self.unescape(escaped_string)
_log.info("not an escaped string: #{escaped_string}") unless escaped?(escaped_string)
@parser ||= URI::RFC2396_Parser.new
@parser.unescape(escaped_string.slice(ESCAPED_PREFIX.length + 2..-2)) # 'escaped:{%2Fescape%2Fstring}' --> '/escape/string'
end
def self.escaped?(string)
string.starts_with?("#{ESCAPED_PREFIX}:{") && string.ends_with?("}")
end
def remove_all_assigned_tos(cat = nil)
# Optional cat arg can be as much of the tail portion (after /miq_alert/assigned_to/) as desired
# Example: If Tags = "/miq_alert/assigned_to/host/tag/managed/environment/prod" and
# "/miq_alert/assigned_to/host/id/4"
# => cat = "host" will remove all the host assignments - both host/id/n and host/tag/...
# => cat = "host/tag" will remove only the host tag assignments.
# => cat = nil will remove all assignments from object
tag_with("", :ns => namespace, :cat => cat)
reload
end
delegate :namespace, :to => :class
module ClassMethods
# get a mapping of alert_sets and the tags they are assigned
#
# the namespace is removed from the front of the objects tag. e.g.:
# If alert_set will have:
# alert_set.tag.name == "/miq_alert_set/assigned_to/vm/tag/managed/environment/test"
# assignments will return
# {assigned: alert_set, assigned_to: "vm/tag/managed/environment/test"}
# and will match:
# vm.tag.name = "/managed/environment/test"
def assignments
# Get all assigned, enabled instances for type klass
records = kind_of?(Class) ? all : self
assignment_map = records.index_by { |a| a.id }
Tag
.includes(:taggings).references(:taggings)
.where("taggings.taggable_type = ? and tags.name like ?", name, "#{namespace}/%")
.each_with_object(Hash.new { |h, k| h[k] = [] }) do |tag, ret|
tag.taggings.each do |tagging|
tag_name = Tag.filter_ns([tag], namespace).first
taggable = assignment_map[tagging.taggable_id]
ret[tag_name] << taggable if taggable
end
end
end
def tag_class(klass)
klass == "VmOrTemplate" ? "vm" : klass.underscore
end
# @param target
# @option options :parents
# @option options :tag_list
def get_assigned_for_target(target, options = {})
_log.debug("Input for get_assigned_for_target id: #{target.id} class: #{target.class}") if target
if options[:parents]
parents = options[:parents]
_log.debug("Parents are passed from parameter")
else
_log.debug("Parents are not passed from parameter")
parents = self::ASSIGNMENT_PARENT_ASSOCIATIONS.flat_map do |rel|
(rel == :my_enterprise ? MiqEnterprise.my_enterprise : target.try(rel)) || []
end
parents << target
end
parents.each { |parent| _log.debug("parent id: #{parent.id} class: #{parent.class}") } if parents.kind_of?(Array)
tlist = parents.collect { |p| "#{p.class.base_model.name.underscore}/id/#{p.id}" } # Assigned directly to parents
if options[:tag_list] # Assigned to target (passed in)
tlist += options[:tag_list]
_log.debug("Using tag list: #{options[:tag_list].join(', ')}")
end
_log.debug("Directly assigned to parents: #{tlist.join(', ')}")
individually_assigned_resources = tlist.flat_map { |t| assignments_cached[t] }.uniq
_log.debug("Individually assigned resources: #{individually_assigned_resources.map { |x| "id:#{x.id} class:#{x.class}" }.join(', ')}")
# look for alert_set running off of tags (not individual tags)
# TODO: we may need to change taggings-related code to use base_model too
tlist = Tagging.where("tags.name like '/managed/%'")
.where(:taggable => parents)
.references(:tag).includes(:tag).map do |t|
"#{tag_class(t.taggable_type)}/tag#{t.tag.name}"
end
_log.debug("Tags assigned to parents: #{tlist.join(', ')}")
tagged_resources = tlist.flat_map { |t| assignments_cached[t] }.uniq
_log.debug("Tagged resources: #{individually_assigned_resources.map { |x| "id:#{x.id} class:#{x.class}" }.join(', ')}")
(individually_assigned_resources + tagged_resources).uniq
end
def namespace
"/#{base_model.name.underscore}/#{NAMESPACE_SUFFIX}"
end
end # module ClassMethods
private
def build_label_tag_path(obj, klass)
name = AssignmentMixin.escape(obj.name)
value = AssignmentMixin.escape(obj.value)
"#{klass.underscore}/label/managed/#{name}/#{value}"
end
def build_object_tag_path(obj, klass = nil)
if obj.kind_of?(ActiveRecord::Base) # obj is a CI
"#{obj.class.base_model.name.underscore}/id/#{obj.id}"
else # obj is the id of an instance of <klass>
raise _("Class must be specified when object is an integer") if klass.nil?
"#{klass.underscore}/id/#{obj}"
end
end
def build_tag_tagging_path(obj, klass)
unless obj.kind_of?(ActiveRecord::Base) # obj is the id of a classification entry instance
id = obj
obj = Classification.find_by(:id => id)
if obj.nil?
_log.warn("Unable to find classification with id [#{id}], skipping assignment")
return nil
end
end
"#{klass.underscore}/tag#{obj.ns}/#{obj.parent.name}/#{obj.name}"
end
end # module AssignmentMixin