Project

General

Profile

« Previous | Next » 

Revision f8d94608

Added by Amos Benari over 11 years ago

  • ID f8d946082e58b60213a27ded3e1e5f5373d976de

fixes #832 - adds parameterized class support

Credits:
This patch is based on the original work of Olivier Favre
<> many many thanks!

This patch adds the following features
  • import class parameters, and try to understand their types
  • support a complex matrix of environments, puppet classes and their
    signature - each class can have a different set of parameters per environment.
  • adds an ignore yaml file 'config/ignored_environments.yml.sample' file were
    users can add regexp or class names that the importer should ignore.
    common usage case for this is classes such as ::config, ::install etc.
  • introduce a new type of smart variable - parameterized.
  • adds complex data types to smart vars, arrays, hashes, json, yaml etc are all supported now.

in order to use the new ENC format for puppet 2.6.5+ you should enable the
Parametrized_Classes_in_ENC and Enable_Smart_Variables_in_ENC within Foreman Settings

This is the initial patch just to get param classes support in, follow-up patches
would include a better UI and the relevant UI updates to host edit page etc.

Signed-off-by: Ohad Levy <>

View differences:

app/models/lookup_key.rb
class LookupKey < ActiveRecord::Base
include Authorization
VALIDATION_TYPES = %w( regexp list )
KEY_TYPES = %w( string boolean integer real array hash yaml json )
VALIDATOR_TYPES = %w( regexp list )
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON', 'yes', 'YES', 'y', 'Y'].to_set
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF', 'no', 'NO', 'n', 'N'].to_set
KEY_DELM = ","
EQ_DELM = "="
serialize :default_value
belongs_to :puppetclass
has_many :environment_classes, :dependent => :destroy
has_many :environments, :through => :environment_classes, :uniq => true
has_one :param_class, :through => :environment_classes, :class_name => 'Puppetclass', :source => :puppetclass
has_many :lookup_values, :dependent => :destroy, :inverse_of => :lookup_key
accepts_nested_attributes_for :lookup_values, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
validates_uniqueness_of :key
validates_presence_of :key, :puppetclass_id
validates_inclusion_of :validator_type, :in => VALIDATION_TYPES, :message => "invalid", :allow_blank => true, :allow_nil => true
validate :validate_range_rule, :validate_range, :validate_list, :validate_regexp
before_validation :validate_and_cast_default_value
validates_uniqueness_of :key, :unless => Proc.new{|p| p.is_param?}
validates_presence_of :key
validates_presence_of :puppetclass_id, :unless => Proc.new {|k| k.is_param?}
validates_inclusion_of :validator_type, :in => VALIDATOR_TYPES, :message => "invalid", :allow_blank => true, :allow_nil => true
validates_inclusion_of :key_type, :in => KEY_TYPES, :message => "invalid", :allow_blank => true, :allow_nil => true
validate :validate_list, :validate_regexp
validates_associated :lookup_values
validate :ensure_type
before_save :sanitize_path
scoped_search :on => :key, :complete_value => true, :default_order => true
scoped_search :in => :puppetclass, :on => :name, :rename => :puppetclass, :complete_value => true
scoped_search :on => :override, :complete_value => {:true => true, :false => false}
scoped_search :in => :param_class, :on => :name, :rename => :puppetclass, :complete_value => true
scoped_search :in => :lookup_values, :on => :value, :rename => :value, :complete_value => true
default_scope :order => 'LOWER(lookup_keys.key)'
def to_param
key
"#{id}-#{key}"
end
def to_s
key
end
#TODO: use SQL coalesce to minimize the amount of queries
def value_for host
path2matches(host).each do |match|
if (v = lookup_values.find_by_match(match))
return v.value
end
end
end if lookup_values.any?
default_value
end
def path
read_attribute(:path) || array2path(Setting["Default_variables_Lookup_Path"])
path = read_attribute(:path)
path.blank? ? array2path(Setting["Default_variables_Lookup_Path"]) : path
end
def path=(v)
return if v == array2path(Setting["Default_variables_Lookup_Path"])
write_attribute(:path, v)
using_default = v.tr("\r","") == array2path(Setting["Default_variables_Lookup_Path"])
write_attribute(:path, using_default ? nil : v)
end
def default_value_before_type_cast
value_before_type_cast default_value
end
def value_before_type_cast val
case validator_type.to_sym
when :json
val = JSON.dump val
when :yaml, :hash
val = YAML.dump val
val.sub! /\A---\s*$\n/, ''
when :array
val = val.inspect
end unless validator_type.blank?
val
end
# Returns the casted value, or raises a TypeError
def cast_validate_value value
method = "cast_value_#{validator_type}".to_sym
return value unless self.respond_to? method, true
self.send(method, value) rescue raise TypeError
end
def as_json(options={})
super({:only => [:key, :description, :default_value, :id]}.merge(options))
super({:only => [:key, :is_param, :required, :override, :description, :default_value, :id]}.merge(options))
end
private
......
self.path = path.tr("\s","").downcase unless path.blank?
end
def validate_range_rule
return true unless (validator_type == 'range')
self.errors.add(:validator_rule, "is invalid") and return false unless validator_rule =~ /^(\d|"[a-z]"|'[a-z]')+\.\.(\d|"[b-z]"|'[b-z]')+$/
end
def array2path array
raise "invalid path" unless array.is_a?(Array)
array.map do |sub_array|
......
end.join("\n")
end
def validate_and_cast_default_value
return true if default_value.nil?
begin
self.default_value = cast_validate_value self.default_value
true
rescue
errors.add(:default_value, "is invalid")
false
end
end
def cast_value_boolean value
return true if TRUE_VALUES.include? value
return false if FALSE_VALUES.include? value
raise TypeError
end
def cast_value_integer value
return value.to_i if value.is_a?(Numeric)
if value.is_a?(String)
if value =~ /^0x[0-9a-f]+$/i
value.to_i(16)
elsif value =~ /^0[0-7]+$/
value.to_i(8)
elsif value =~ /^-?\d+$/
value.to_i
else
raise TypeError
end
end
end
def cast_value_real value
return value if value.is_a? Numeric
if value.is_a?(String)
if value =~ /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$/
value.to_f
else
cast_value_integer value
end
end
end
def load_yaml_or_json value
return value unless value.is_a? String
begin
JSON.load value
rescue
YAML.load value
end
end
def cast_value_array value
return value if value.is_a? Array
return value.to_a if not value.is_a? String and value.is_a? Enumerable
value = load_yaml_or_json value
raise TypeError unless value.is_a? Array
value
end
def cast_value_hash value
return value if value.is_a? Hash
value = load_yaml_or_json value
raise TypeError unless value.is_a? Hash
value
end
def cast_value_yaml value
value = YAML.load value
end
def cast_value_json value
value = JSON.load value
end
def ensure_type
if puppetclass_id.present? and is_param?
self.errors.add(:base, 'Global variable or class Parameter, not both')
end
end
def validate_regexp
return true unless (validator_type == 'regexp')
errors.add(:default_value, "is invalid") and return false unless (default_value =~ /#{validator_rule}/)
end
def validate_range
return true unless (validator_type == 'range')
errors.add(:default_value, "not within range #{validator_rule}") and return false unless eval(validator_rule).include?(default_value)
end
def validate_list
return true unless (validator_type == 'list')
errors.add(:default_value, "not in list") and return false unless validator_rule.split(KEY_DELM).map(&:strip).include?(default_value)
errors.add(:default_value, "#{default_value} is not one of #{validator_rule}") and return false unless validator_rule.split(KEY_DELM).map(&:strip).include?(default_value)
end
end

Also available in: Unified diff