|
module Classification
|
|
class Base
|
|
delegate :hostgroup, :environment_id, :puppetclass_ids, :classes,
|
|
:to => :host
|
|
|
|
def initialize(args = {})
|
|
@host = args[:host]
|
|
@safe_render = SafeRender.new(:variables => {:host => host})
|
|
end
|
|
|
|
#override to return the relevant enc data and format
|
|
def enc
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def inherited_values
|
|
values_hash :skip_fqdn => true
|
|
end
|
|
|
|
protected
|
|
|
|
attr_reader :host
|
|
|
|
#override this method to return the relevant parameters for a given set of classes
|
|
def class_parameters
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def possible_value_orders
|
|
class_parameters.select do |key|
|
|
# take only keys with actual values
|
|
key.lookup_values_count > 0 # we use counter cache, so its safe to make that query
|
|
end.map(&:path_elements).flatten(1).uniq
|
|
end
|
|
|
|
def values_hash(options = {})
|
|
values = Hash.new { |h,k| h[k] = {} }
|
|
all_lookup_values = LookupValue.where(:match => path2matches).where(:lookup_key_id => class_parameters)
|
|
class_parameters.each do |key|
|
|
lookup_values_for_key = all_lookup_values.where(:lookup_key_id => key.id, :use_puppet_default => false).includes(:lookup_key)
|
|
sorted_lookup_values = lookup_values_for_key.sort_by do |lv|
|
|
matcher_key = ''
|
|
matcher_value = ''
|
|
lv.match.split(LookupKey::KEY_DELM).each do |match_key|
|
|
element = match_key.split(LookupKey::EQ_DELM).first
|
|
matcher_key += element + ','
|
|
if element == 'hostgroup'
|
|
matcher_value = match_key.split(LookupKey::EQ_DELM).last
|
|
end
|
|
end
|
|
# prefer matchers in order of the path, then more specific matches (i.e. hostgroup children)
|
|
[key.path.index(matcher_key.chomp(',')), -1 * matcher_value.length]
|
|
end
|
|
value = nil
|
|
if key.merge_overrides
|
|
default = key.merge_default ? key.default_value : nil
|
|
case key.key_type
|
|
when "array"
|
|
value = update_array_matcher(default, key.avoid_duplicates, sorted_lookup_values, options)
|
|
when "hash"
|
|
value = update_hash_matcher(default, sorted_lookup_values, options)
|
|
else
|
|
raise "merging enabled for non mergeable key #{key.key}"
|
|
end
|
|
else
|
|
value = update_generic_matcher(sorted_lookup_values, options)
|
|
end
|
|
|
|
values[key.id][key.key] = value if value.present?
|
|
end
|
|
values
|
|
end
|
|
|
|
def value_of_key(key, values)
|
|
value = if values[key.id] and values[key.id][key.to_s]
|
|
{:value => values[key.id][key.to_s][:value]}
|
|
else
|
|
default_value_method = %w(yaml json).include?(key.key_type) ? :default_value_before_type_cast : :default_value
|
|
{:value => key.send(default_value_method), :managed => key.use_puppet_default}
|
|
end
|
|
|
|
return nil if value[:managed]
|
|
needs_late_validation = key.contains_erb?(value[:value])
|
|
value = @safe_render.parse(value[:value])
|
|
value = type_cast(key, value)
|
|
validate_lookup_value(key, value) if needs_late_validation
|
|
value
|
|
end
|
|
|
|
def hostgroup_matches
|
|
@hostgroup_matches ||= matches_for_hostgroup
|
|
end
|
|
|
|
def matches_for_hostgroup
|
|
matches = []
|
|
if hostgroup
|
|
path = hostgroup.to_label
|
|
while path.include?("/")
|
|
path = path[0..path.rindex("/")-1]
|
|
matches << "hostgroup#{LookupKey::EQ_DELM}#{path}"
|
|
end
|
|
end
|
|
matches
|
|
end
|
|
|
|
# Generate possible lookup values type matches to a given host
|
|
def path2matches
|
|
matches = []
|
|
possible_value_orders.each do |rule|
|
|
match = Array.wrap(rule).map do |element|
|
|
"#{element}#{LookupKey::EQ_DELM}#{attr_to_value(element)}"
|
|
end
|
|
matches << match.join(LookupKey::KEY_DELM)
|
|
|
|
hostgroup_matches.each do |hostgroup_match|
|
|
match[match.index { |m| m =~ /hostgroup\s*=/ }]=hostgroup_match
|
|
matches << match.join(LookupKey::KEY_DELM)
|
|
end if Array.wrap(rule).include?("hostgroup") && Setting["host_group_matchers_inheritance"]
|
|
end
|
|
matches
|
|
end
|
|
|
|
# translates an element such as domain to its real value per host
|
|
# tries to find the host attribute first, parameters and then fallback to a puppet fact.
|
|
def attr_to_value(element)
|
|
# direct host attribute
|
|
return host.send(element) if host.respond_to?(element)
|
|
# host parameter
|
|
return host.host_params[element] if host.host_params.include?(element)
|
|
# fact attribute
|
|
if (fn = host.fact_names.where(:name => element).first)
|
|
return FactValue.where(:host_id => host.id, :fact_name_id => fn.id).first.value
|
|
end
|
|
end
|
|
|
|
def path_elements(path = nil)
|
|
path.split.map do |paths|
|
|
paths.split(LookupKey::KEY_DELM).map do |element|
|
|
element
|
|
end
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def validate_lookup_value(key, value)
|
|
lookup_value = key.lookup_values.build(:value => value)
|
|
return true if lookup_value.validate_value
|
|
raise "Invalid value '#{value}' of parameter #{key.id} '#{key.key}'"
|
|
end
|
|
|
|
def type_cast(key, value)
|
|
Foreman::Parameters::Caster.new(key, :attribute_name => :value, :to => key.key_type, :value => value).cast
|
|
rescue TypeError
|
|
Rails.logger.warn "Unable to type cast #{value} to #{key.key_type}"
|
|
end
|
|
|
|
def get_element_and_element_name(lookup_value)
|
|
element = ''
|
|
element_name = ''
|
|
lookup_value.match.split(LookupKey::KEY_DELM).each do |match_key|
|
|
lv_element, lv_element_name = match_key.split(LookupKey::EQ_DELM)
|
|
element += lv_element + ','
|
|
element_name += lv_element_name + ','
|
|
end
|
|
[element.chomp(','), element_name.chomp(',')]
|
|
end
|
|
|
|
def update_generic_matcher(lookup_values, options)
|
|
computed_lookup_value = nil
|
|
lookup_values.each do |lookup_value|
|
|
element, element_name = get_element_and_element_name(lookup_value)
|
|
next if (options[:skip_fqdn] && element=="fqdn")
|
|
value_method = %w(yaml json).include?(lookup_value.lookup_key.key_type) ? :value_before_type_cast : :value
|
|
computed_lookup_value = {:value => lookup_value.send(value_method), :element => element,
|
|
:element_name => element_name}
|
|
break
|
|
end
|
|
computed_lookup_value
|
|
end
|
|
|
|
def update_array_matcher(default, should_avoid_duplicates, lookup_values, options)
|
|
elements = []
|
|
values = []
|
|
element_names = []
|
|
|
|
unless default.nil?
|
|
values += default
|
|
elements << s_("LookupKey|Default value")
|
|
element_names << s_("LookupKey|Default value")
|
|
end
|
|
|
|
lookup_values.each do |lookup_value|
|
|
element, element_name = get_element_and_element_name(lookup_value)
|
|
next if (options[:skip_fqdn] && element=="fqdn")
|
|
elements << element
|
|
element_names << element_name
|
|
if should_avoid_duplicates
|
|
values |= lookup_value.value
|
|
else
|
|
values += lookup_value.value
|
|
end
|
|
end
|
|
|
|
return nil unless values.present?
|
|
{:value => values, :element => elements, :element_name => element_names}
|
|
end
|
|
|
|
def update_hash_matcher(default, lookup_values, options)
|
|
elements = []
|
|
values = {}
|
|
element_names = []
|
|
|
|
unless default.nil?
|
|
values = default
|
|
elements << s_("LookupKey|Default value")
|
|
element_names << s_("LookupKey|Default value")
|
|
end
|
|
|
|
# to make sure seep merge overrides by priority, putting in the values with the lower priority first
|
|
# and then merging with higher priority
|
|
lookup_values.reverse.each do |lookup_value|
|
|
element, element_name = get_element_and_element_name(lookup_value)
|
|
next if (options[:skip_fqdn] && element=="fqdn")
|
|
elements << element
|
|
element_names << element_name
|
|
values.deep_merge!(lookup_value.value)
|
|
end
|
|
|
|
return nil unless values.present?
|
|
{:value => values, :element => elements, :element_name => element_names}
|
|
end
|
|
end
|
|
end
|