Skip to content

Commit

Permalink
fixes #10232 - moving validations and casting out of lookup key and v…
Browse files Browse the repository at this point in the history
…alue
  • Loading branch information
unorthodoxgeek authored and Dominic Cleal committed Aug 19, 2015
1 parent bb6aa56 commit 9f88e8a
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 99 deletions.
88 changes: 9 additions & 79 deletions app/models/lookup_key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ def audit_class
:reject_if => :reject_invalid_lookup_values,
:allow_destroy => true

before_validation :validate_and_cast_default_value, :unless => Proc.new{|p| p.use_puppet_default }
before_validation :cast_default_value, :unless => Proc.new{|p| p.use_puppet_default }
validates :key, :uniqueness => {:scope => :is_param }, :unless => Proc.new{|p| p.is_param?}

validates :key, :presence => true
validates :puppetclass, :presence => true, :unless => Proc.new {|k| k.is_param?}
validates :validator_type, :inclusion => { :in => VALIDATOR_TYPES, :message => N_("invalid")}, :allow_blank => true, :allow_nil => true
validates :key_type, :inclusion => {:in => KEY_TYPES, :message => N_("invalid")}, :allow_blank => true, :allow_nil => true
validate :validate_list, :validate_regexp
validate :validate_default_value
validates_associated :lookup_values
validate :ensure_type, :disable_merge_overrides, :disable_avoid_duplicates

Expand Down Expand Up @@ -154,13 +154,6 @@ def value_before_type_cast(val)
val
end

# Returns the casted value, or raises a TypeError
def cast_validate_value(value)
method = "cast_value_#{key_type}".to_sym
return value unless self.respond_to? method, true
self.send(method, value) rescue raise TypeError
end

def path_elements
path.split.map do |paths|
paths.split(KEY_DELM).map do |element|
Expand Down Expand Up @@ -218,50 +211,17 @@ def array2path(array)
end.join("\n")
end

def validate_and_cast_default_value
def cast_default_value
return true if default_value.nil? || contains_erb?(default_value)
begin
self.default_value = cast_validate_value self.default_value
Foreman::Parameters::Caster.new(self, :attribute_name => :default_value, :to => key_type).cast!
true
rescue
errors.add(:default_value, _("is invalid"))
false
end
end

def cast_value_boolean(value)
casted = Foreman::Cast.to_bool(value)
raise TypeError if casted.nil?
casted
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 =~ /\A[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?\Z/
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
Expand All @@ -271,47 +231,17 @@ def load_yaml_or_json(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)
YAML.load value
end

def cast_value_json(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 if (validator_type != 'regexp' || (contains_erb?(default_value) && Setting[:interpolate_erb_in_parameters]))
valid = (default_value =~ /#{validator_rule}/)
errors.add(:default_value, _("is invalid")) unless valid
valid
end

def validate_list
return true if (validator_type != 'list' || (contains_erb?(default_value) && Setting[:interpolate_erb_in_parameters]))
valid = validator_rule.split(KEY_DELM).map(&:strip).include?(default_value)
errors.add(:default_value, _("%{default_value} is not one of %{validator_rule}") % { :default_value => default_value, :validator_rule => validator_rule }) unless valid
valid
def validate_default_value
Foreman::Parameters::Validator.new(self,
:type => validator_type,
:validate_with => validator_rule,
:getter => :default_value).validate!
end

def disable_merge_overrides
Expand Down
26 changes: 10 additions & 16 deletions app/models/lookup_value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ class LookupValue < ActiveRecord::Base
validate :value_present?
delegate :key, :to => :lookup_key
before_validation :sanitize_match

before_validation :validate_and_cast_value, :unless => Proc.new{|p| p.use_puppet_default }
validate :validate_list, :validate_regexp, :ensure_fqdn_exists, :ensure_hostgroup_exists
validate :validate_value, :ensure_fqdn_exists, :ensure_hostgroup_exists

attr_accessor :host_or_hostgroup
attr_writer :managed_id, :hostgroup_id
Expand Down Expand Up @@ -46,6 +47,13 @@ def value_before_type_cast
lookup_key.value_before_type_cast self.value
end

def validate_value
Foreman::Parameters::Validator.new(self,
:type => lookup_key.validator_type,
:validate_with => lookup_key.validator_rule,
:getter => :value).validate!
end

private

#TODO check multi match with matchers that have space (hostgroup = web servers,environment = production)
Expand All @@ -57,7 +65,7 @@ def validate_and_cast_value
return true if self.marked_for_destruction? or !self.value.is_a? String
begin
unless self.lookup_key.contains_erb?(value)
self.value = lookup_key.cast_validate_value self.value
Foreman::Parameters::Caster.new(self, :attribute_name => :value, :to => lookup_key.key_type).cast!
end
true
rescue StandardError, SyntaxError => e
Expand All @@ -67,20 +75,6 @@ def validate_and_cast_value
end
end

def validate_regexp
return true if (lookup_key.validator_type != 'regexp' || (lookup_key.contains_erb?(value) && Setting[:interpolate_erb_in_parameters]))
valid = (value =~ /#{lookup_key.validator_rule}/)
errors.add(:value, _("is invalid")) unless valid
valid
end

def validate_list
return true if (lookup_key.validator_type != 'list' || (lookup_key.contains_erb?(value) && Setting[:interpolate_erb_in_parameters]))
valid = lookup_key.validator_rule.split(LookupKey::KEY_DELM).map(&:strip).include?(value)
errors.add(:value, _("%{value} is not one of %{rules}") % { :value => value, :rules => lookup_key.validator_rule }) unless valid
valid
end

def ensure_fqdn_exists
md = ensure_matcher(/fqdn=(.*)/)
return md if md == true || md == false
Expand Down
4 changes: 2 additions & 2 deletions app/services/classification/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,12 @@ def path_elements(path = nil)

def validate_lookup_value(key, value)
lookup_value = key.lookup_values.build(:value => value)
return true if lookup_value.send(:validate_list) && lookup_value.send(:validate_regexp)
return true if lookup_value.validate_value
raise "Invalid value '#{value}' of parameter #{key.id} '#{key.key}'"
end

def type_cast(key, value)
key.cast_validate_value(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
Expand Down
122 changes: 122 additions & 0 deletions app/services/foreman/parameters/caster.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
module Foreman
module Parameters
class Caster
attr_reader :value

def initialize(item, options = {})
defaults = {
:attribute_name => :value,
:to => :string
}
options.reverse_merge!(defaults)
@item, @options = item, options
@value = @options[:value] || @item.send(@options[:attribute_name])
end

def cast!
@item.send("#{@options[:attribute_name]}=", casted_value)
end

def cast
casted_value
end

private

def casted_value
case @options[:to].to_s
when "string"
cast_string
when "integer"
cast_integer
when "real"
cast_real
when "boolean"
cast_boolean
when "array"
cast_array
when "hash"
cast_hash
when "json"
cast_json
when "yaml"
cast_yaml
when nil, ""
value
else
Rails.logger.warn("Unable to type cast #{value} to #{@options[:to]}")
raise TypeError
end
end

def cast_string
value.to_s
end

def cast_boolean
val = Foreman::Cast.to_bool(value)
return val if [true, false].include?(val)
raise TypeError
end

def cast_integer
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_real
return value if value.is_a? Numeric
if value.is_a?(String)
if value =~ /\A[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?\Z/
value.to_f
else
cast_value_integer value
end
end
end

def cast_array
return value if value.is_a? Array
return value.to_a if not value.is_a? String and value.is_a? Enumerable
val = load_yaml_or_json
raise TypeError unless val.is_a? Array
val
end

def cast_hash
return value if value.is_a? Hash
val = load_yaml_or_json
raise TypeError unless val.is_a? Hash
val
end

def cast_json
JSON.load value
end

def cast_yaml
YAML.load value
end

def load_yaml_or_json
return value unless value.is_a? String
begin
JSON.load value
rescue
YAML.load value
end
end
end
end
end
56 changes: 56 additions & 0 deletions app/services/foreman/parameters/validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module Foreman
module Parameters
class Validator
KEY_DELM = ","

def initialize(item, options = {})
@item, @options = item, options
end

def validate!
case @options[:type].to_s
when "regexp"
validate_regexp
when "list", "array"
validate_list
else
return true
end
end

private

def value
@item.send(@options[:getter])
end

def validate_regexp
return true if contains_erb?(value) && Setting[:interpolate_erb_in_parameters]

unless value =~ /#{@options[:validate_with]}/
add_error(_("is invalid"))
return false
end
true
end

def validate_list
return true if contains_erb?(value) && Setting[:interpolate_erb_in_parameters]

unless @options[:validate_with].split(KEY_DELM).map(&:strip).include?(value)
add_error(_("%{value} is not one of %{rules}") % { :value => value, :rules => @options[:validate_with] })
return false
end
true
end

def add_error(message)
@item.errors.add(@options[:getter], message)
end

def contains_erb?(value)
value =~ /<%.*%>/
end
end
end
end
Loading

0 comments on commit 9f88e8a

Please sign in to comment.