Revision f8d94608
Added by Amos Benari over 11 years ago
- ID f8d946082e58b60213a27ded3e1e5f5373d976de
app/controllers/lookup_keys_controller.rb | ||
---|---|---|
private
|
||
def find_by_key
|
||
if params[:id]
|
||
if params[:id].to_i == 0
|
||
@lookup_key = LookupKey.find_by_key(params[:id])
|
||
else
|
||
@lookup_key = LookupKey.find(params[:id])
|
||
end
|
||
@lookup_key = LookupKey.find(params[:id])
|
||
not_found and return if @lookup_key.blank?
|
||
end
|
||
end
|
app/controllers/puppetclasses_controller.rb | ||
---|---|---|
format.html do
|
||
@puppetclasses = values.paginate :page => params[:page], :include => [:environments, :hostgroups]
|
||
@host_counter = Host.count(:group => :puppetclass_id, :joins => :puppetclasses, :conditions => {:puppetclasses => {:id => @puppetclasses}})
|
||
@keys_counter = LookupKey.count(:group => :puppetclass_id, :conditions => {:puppetclass_id => @puppetclasses})
|
||
@keys_counter = Puppetclass.joins(:class_params).select('distinct environment_classes.lookup_key_id').count(:group => 'name')
|
||
end
|
||
format.json { render :json => Puppetclass.classes2hash(values.all(:select => "name, id")) }
|
||
end
|
app/helpers/layout_helper.rb | ||
---|---|---|
end
|
||
end
|
||
|
||
def line_count (f, attr)
|
||
rows = f.object.try(attr).to_s.lines.count rescue 1
|
||
rows == 0 ? 1 : rows
|
||
end
|
||
|
||
def textarea_f(f, attr, options = {})
|
||
field(f, attr, options) do
|
||
options[:rows] = line_count(f, attr) if options[:rows] == :auto
|
||
f.text_area attr, options
|
||
end
|
||
end
|
||
... | ... | |
end
|
||
|
||
def checkbox_f(f, attr, options = {})
|
||
text = options.delete(:help_inline)
|
||
text = options.delete(:help_text)
|
||
inline = options.delete(:help_inline)
|
||
field(f, attr, options) do
|
||
label_tag('', :class=>'checkbox') do
|
||
f.check_box(attr, options) + " #{text} "
|
||
help_inline = inline.blank? ? '' : content_tag(:span, inline, :class => "help-inline")
|
||
f.check_box(attr, options) + " #{text} " + help_inline.html_safe
|
||
end
|
||
end
|
||
end
|
app/helpers/lookup_keys_helper.rb | ||
---|---|---|
options[:form_builder_local] ||= :f
|
||
options[:form_builder_attrs] ||= {}
|
||
|
||
content_tag(:div, :id => "#{association}_fields_template", :style => "display: none;") do
|
||
content_tag(:div, :class => "#{association}_fields_template", :style => "display: none;") do
|
||
form_builder.fields_for(association, options[:object], :child_index => "new_#{association}") do |f|
|
||
render(:partial => options[:partial], :locals => { options[:form_builder_local] => f }.merge(options[:form_builder_attrs]))
|
||
end
|
||
end
|
||
end
|
||
|
||
def show_puppet_class f
|
||
# In case of a new smart-var inside a puppetclass (REST nesting only), or a class parameter:
|
||
# Show the parent puppetclass as a context, but permit no change.
|
||
if params["puppetclass_id"]
|
||
select_f f, :puppetclass_id, [Puppetclass.find(params["puppetclass_id"])], :id, :to_label, {}, {:label => "Puppet class", :disabled => true}
|
||
elsif f.object.is_param && f.object.param_class
|
||
field(f, :puppetclass_id, :label => 'Puppet Class') do
|
||
content_tag(:input, nil, :value => f.object.param_class, :type => 'text', :disabled => true)
|
||
end
|
||
else # new smart-var with no particular context
|
||
# Give a select for choosing the parent puppetclass
|
||
select_f(f, :puppetclass_id, Puppetclass.all, :id, :to_label, { :include_blank => 'None' }, {:label => "Puppet class"})
|
||
end unless @puppetclass # nested smart-vars form in a tab of puppetclass/_form: no edition allowed, and the puppetclass is already visible as a context
|
||
end
|
||
|
||
def param_type_selector f
|
||
selectable_f f, :key_type, options_for_select(LookupKey::KEY_TYPES, f.object.key_type),{},
|
||
{ :disabled => (f.object.is_param && !f.object.override), :label => "Type", :class => "medium",
|
||
:help_inline => popover("?","<dl>
|
||
<dt>String</dt> <dd>Everything is taken as a string.</dd>
|
||
<dt>Boolean</dt> <dd>Common representation of boolean values are accepted.</dd>
|
||
<dt>Integer</dt> <dd>Integer numbers only, can be negative.</dd>
|
||
<dt>Real</dt> <dd>Accept any numerical input.</dd>
|
||
<dt>Array</dt> <dd>A valid JSON or YAML input, that must evaluate to an array.</dd>
|
||
<dt>Hash</dt> <dd>A valid JSON or YAML input, that must evaluate to an object/map/dict/hash.</dd>
|
||
<dt>YAML</dt> <dd>Any valid YAML input.</dd>
|
||
<dt>JSON</dt> <dd>Any valid JSON input.</dd>
|
||
</dl>", :title => "How values are validated").html_safe}
|
||
end
|
||
|
||
def validator_type_selector f
|
||
selectable_f f, :validator_type, options_for_select(LookupKey::VALIDATOR_TYPES, f.object.validator_type),{:include_blank => true},
|
||
{ :disabled => (f.object.is_param && !f.object.override), :label => "Validator Type", :class => "medium",
|
||
:help_inline => popover("?","<dl>
|
||
<dt>List</dt> <dd>A list of the allowed values, specified in the Validator rule field.</dd>
|
||
<dt>Regexp</dt> <dd>Validates the input with the regular expression in the Validator rule field.</dd>
|
||
</dl>", :title => "Validation types").html_safe}
|
||
end
|
||
|
||
end
|
app/helpers/puppetclasses_and_environments_helper.rb | ||
---|---|---|
elsif pcs.delete "_destroy_"
|
||
"Deleted environment #{env} and " + pcs.to_sentence
|
||
else
|
||
pcs.to_sentence
|
||
pretty_print(pcs.is_a?(Hash) ? pcs.keys : pcs)
|
||
end
|
||
end
|
||
|
||
def import_proxy_select hash
|
||
proxies = Environment.find_import_proxies
|
||
toolbar_action_buttons(
|
||
proxies.map do |proxy|
|
||
SmartProxy.puppet_proxies.map do |proxy|
|
||
display_link_if_authorized("Import from #{proxy.name}", hash.merge(:proxy => proxy))
|
||
end.flatten
|
||
)
|
||
end
|
||
|
||
private
|
||
def pretty_print classes
|
||
hash = { }
|
||
classes.each do |klass|
|
||
if (mod = klass.gsub(/::.*/, ""))
|
||
hash[mod] ||= []
|
||
hash[mod] << klass
|
||
else
|
||
next
|
||
end
|
||
end
|
||
hash.keys.sort.map do |key|
|
||
link_to_function key, { :rel => "popover", "data-content" => hash[key].sort.join('<br>').html_safe, "data-original-title" => key }
|
||
end.to_sentence.html_safe
|
||
|
||
end
|
||
|
||
end
|
app/models/environment.rb | ||
---|---|---|
class Environment < ActiveRecord::Base
|
||
has_and_belongs_to_many :puppetclasses
|
||
has_many :environment_classes, :dependent => :destroy
|
||
has_many :puppetclasses, :through => :environment_classes, :uniq => true
|
||
has_many :hosts
|
||
|
||
validates_presence_of :name
|
||
validates_uniqueness_of :name
|
||
validates_format_of :name, :with => /^[\w\d]+$/, :message => "is alphanumeric and cannot contain spaces"
|
||
... | ... | |
|
||
class << self
|
||
|
||
#TODO: this needs to be removed, as PuppetDOC generation no longer works
|
||
# if the manifests are not on the foreman host
|
||
# returns an hash of all puppet environments and their relative paths
|
||
def puppetEnvs proxy = nil
|
||
#TODO: think of a better way to model multiple puppet proxies
|
||
url = (proxy || find_import_proxies.first).try(:url)
|
||
|
||
url = (proxy || SmartProxy.puppet_proxies.first).try(:url)
|
||
raise "Can't find a valid Foreman Proxy with a Puppet feature" if url.blank?
|
||
proxy = ProxyAPI::Puppet.new :url => url
|
||
HashWithIndifferentAccess[proxy.environments.map { |e| [e, proxy.classes(e)] }]
|
||
end
|
||
|
||
# Imports all Environments and classes from Puppet modules
|
||
def importClasses proxy_id
|
||
# Build two hashes representing the on-disk and in-database, env to classes associations
|
||
# Create a representation of the puppet configuration where the environments are hash keys and the classes are sorted lists
|
||
disk_tree = puppetEnvs SmartProxy.find(proxy_id)
|
||
disk_tree.default = []
|
||
|
||
# Create a representation of the foreman configuration where the environments are hash keys and the classes are sorted lists
|
||
db_tree = HashWithIndifferentAccess[Environment.all.map { |e| [e.name, e.puppetclasses.select(:name).map(&:name)] }]
|
||
db_tree.default = []
|
||
|
||
changes = { "new" => { }, "obsolete" => { } }
|
||
# Generate the difference between the on-disk and database configuration
|
||
for env in db_tree.keys
|
||
# Show the environment if there are classes in the db that do not exist on disk
|
||
# OR if there is no mention of the class on-disk
|
||
surplus_db_classes = db_tree[env] - disk_tree[env]
|
||
surplus_db_classes << "_destroy_" unless disk_tree.has_key?(env) # We need to distinguish between an empty and an obsolete env
|
||
changes["obsolete"][env] = surplus_db_classes if surplus_db_classes.size > 0
|
||
end
|
||
for env in disk_tree.keys
|
||
extra_disk_classes = disk_tree[env] - db_tree[env]
|
||
# Show the environment if there are new classes compared to the db
|
||
# OR if the environment has no puppetclasses but does not exist in the db
|
||
changes["new"][env] = extra_disk_classes if (extra_disk_classes.size > 0 or (disk_tree[env].size == 0 and Environment.find_by_name(env).nil?))
|
||
end
|
||
|
||
# Remove environments that are in config/ignored_environments.yml
|
||
ignored_file = File.join(Rails.root.to_s, "config", "ignored_environments.yml")
|
||
if File.exist? ignored_file
|
||
ignored = YAML.load_file ignored_file
|
||
for env in ignored[:new]
|
||
changes["new"].delete env
|
||
end
|
||
for env in ignored[:obsolete]
|
||
changes["obsolete"].delete env
|
||
end
|
||
end
|
||
changes
|
||
HashWithIndifferentAccess[proxy.environments.map { |e|
|
||
[e, HashWithIndifferentAccess[proxy.classes(e).map {|k|
|
||
klass = k.keys.first
|
||
[klass, k[klass]["params"]]
|
||
}]]
|
||
}]
|
||
end
|
||
|
||
# Update the environments and puppetclasses based upon the user's selection
|
||
# It does a best attempt and can fail to perform all operations due to the
|
||
# user requesting impossible selections. Repeat the operation if errors are
|
||
# shown, after fixing the request.
|
||
# +changed+ : Hash with two keys: :new and :obsolete.
|
||
# changed[:/new|obsolete/] is and Array of Strings
|
||
# Returns : Array of Strings containing all record errors
|
||
def obsolete_and_new changed
|
||
changed ||= { }
|
||
@import_errors = []
|
||
|
||
# Now we add environments and associations
|
||
for env_str in changed[:new].keys
|
||
env = Environment.find_or_create_by_name env_str
|
||
if env.valid? and !env.new_record?
|
||
begin
|
||
pclasses = eval(changed[:new][env_str])
|
||
rescue => e
|
||
@import_errors << "Failed to eval #{changed[:new][env_str]} as an array:" + e.message
|
||
next
|
||
end
|
||
for pclass in pclasses
|
||
pc = Puppetclass.find_or_create_by_name pclass
|
||
if pc.errors.empty?
|
||
env.puppetclasses << pc
|
||
else
|
||
@import_errors += pc.errors.map(&:to_s)
|
||
end
|
||
end
|
||
env.save!
|
||
else
|
||
@import_errors << "Unable to find or create environment #{env_str} in the foreman database"
|
||
end
|
||
end if changed[:new]
|
||
|
||
# Remove the obsoleted stuff
|
||
for env_str in changed[:obsolete].keys
|
||
env = Environment.find_by_name env_str
|
||
if env
|
||
begin
|
||
pclasses = eval(changed[:obsolete][env_str])
|
||
rescue => e
|
||
@import_errors << "Failed to eval #{changed[:obsolete][env_str]} as an array:" + e.message
|
||
next
|
||
end
|
||
pclass = ""
|
||
for pclass in pclasses
|
||
unless pclass == "_destroy_"
|
||
pc = Puppetclass.find_by_name pclass
|
||
if pc.nil?
|
||
@import_errors << "Unable to find puppet class #{pclass} in the foreman database"
|
||
else
|
||
env.puppetclasses.delete pc
|
||
unless pc.environments.any? or pc.hosts.any?
|
||
pc.destroy
|
||
@import_errors += pc.errors.full_messages unless pc.errors.empty?
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if pclasses.include? "_destroy_"
|
||
env.destroy
|
||
@import_errors += env.errors.full_messages unless env.errors.empty?
|
||
else
|
||
env.save!
|
||
end
|
||
else
|
||
@import_errors << "Unable to find environment #{env_str} in the foreman database"
|
||
end
|
||
end if changed[:obsolete]
|
||
|
||
@import_errors
|
||
end
|
||
|
||
def find_import_proxies
|
||
SmartProxy.joins(:features).where(:features => {:name => 'Puppet'})
|
||
end
|
||
end
|
||
|
||
def as_json(options={ })
|
app/models/environment_class.rb | ||
---|---|---|
class EnvironmentClass < ActiveRecord::Base
|
||
belongs_to :environment
|
||
belongs_to :puppetclass
|
||
belongs_to :lookup_key
|
||
validates_uniqueness_of :lookup_key_id, :scope => [:environment_id, :puppetclass_id]
|
||
validates_presence_of :puppetclass_id, :environment_id
|
||
|
||
scope :parameters_for_class, lambda {|puppetclasses_ids, environment_id|
|
||
all_parameters_for_class(puppetclasses_ids, environment_id).where(:lookup_keys => {:override => true})
|
||
}
|
||
scope :all_parameters_for_class, lambda {|puppetclasses_ids, environment_id|
|
||
where(:puppetclass_id => puppetclasses_ids, :environment_id => environment_id).
|
||
where('lookup_key_id is NOT NULL').
|
||
includes(:lookup_key)
|
||
}
|
||
|
||
#TODO move these into scopes?
|
||
def self.is_in_any_environment(puppetclass, lookup_key)
|
||
EnvironmentClass.where(:puppetclass_id => puppetclass, :lookup_key_id => lookup_key ).count > 0
|
||
end
|
||
|
||
def self.key_in_environment(env, puppetclass, lookup_key)
|
||
EnvironmentClass.where(:environment_id => env, :puppetclass_id => puppetclass, :lookup_key_id => lookup_key ).first
|
||
end
|
||
end
|
app/models/host.rb | ||
---|---|---|
:provider
|
||
end
|
||
|
||
attr_reader :cached_host_params, :cached_lookup_keys_params
|
||
attr_reader :cached_host_params
|
||
|
||
scope :recent, lambda { |*args| {:conditions => ["last_report > ?", (args.first || (Setting[:puppet_interval] + 5).minutes.ago)]} }
|
||
scope :out_of_sync, lambda { |*args| {:conditions => ["last_report < ? and enabled != ?", (args.first || (Setting[:puppet_interval] + 5).minutes.ago), false]} }
|
||
... | ... | |
end
|
||
param.update self.params
|
||
|
||
classes = if Setting[:Parametrized_Classes_in_ENC] && Setting[:Enable_Smart_Variables_in_ENC]
|
||
lookup_keys_class_params
|
||
else
|
||
self.puppetclasses_names
|
||
end
|
||
|
||
info_hash = {}
|
||
info_hash['classes'] = self.puppetclasses_names
|
||
info_hash['classes'] = classes
|
||
info_hash['parameters'] = param
|
||
info_hash['environment'] = param["foreman_env"] if Setting["enc_environment"]
|
||
|
||
... | ... | |
@cached_host_params = hp
|
||
end
|
||
|
||
def lookup_keys_params
|
||
return cached_lookup_keys_params unless cached_lookup_keys_params.blank?
|
||
p = {}
|
||
# lookup keys
|
||
if Setting["Enable_Smart_Variables_in_ENC"]
|
||
klasses = puppetclasses.map(&:id)
|
||
klasses += hostgroup.classes.map(&:id) if hostgroup
|
||
LookupKey.all(:conditions => {:puppetclass_id =>klasses.flatten } ).each do |k|
|
||
p[k.to_s] = k.value_for(self)
|
||
end unless klasses.empty?
|
||
end
|
||
@cached_lookup_keys_params = p
|
||
end
|
||
|
||
def self.importHostAndFacts yaml
|
||
facts = YAML::load yaml
|
||
case facts
|
||
... | ... | |
end
|
||
|
||
private
|
||
def lookup_keys_params
|
||
return {} unless Setting["Enable_Smart_Variables_in_ENC"]
|
||
|
||
p = {}
|
||
klasses = all_puppetclasses.map(&:id).flatten
|
||
LookupKey.where(:puppetclass_id => klasses ).each do |k|
|
||
p[k.to_s] = k.value_for(self)
|
||
end unless klasses.empty?
|
||
p
|
||
end
|
||
|
||
def lookup_keys_class_params
|
||
p={}
|
||
classes = all_puppetclasses
|
||
keys = EnvironmentClass.parameters_for_class(classes.map(&:id), environment_id).group_by(&:puppetclass_id)
|
||
classes.each do |klass|
|
||
p[klass.name] = nil
|
||
keys[klass.id].map(&:lookup_key).each do |lookup_key|
|
||
p[klass.name] ||= {}
|
||
value = lookup_key.value_for(self)
|
||
p[klass.name].merge!({lookup_key.key => value})
|
||
end if keys[klass.id]
|
||
end
|
||
p
|
||
end
|
||
|
||
# align common mac and ip address input
|
||
def normalize_addresses
|
||
# a helper for variable scoping
|
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
|
app/models/lookup_value.rb | ||
---|---|---|
class LookupValue < ActiveRecord::Base
|
||
include Authorization
|
||
belongs_to :lookup_key
|
||
belongs_to :lookup_key, :counter_cache => true
|
||
validates_uniqueness_of :match, :scope => :lookup_key_id
|
||
validates_presence_of :match, :value
|
||
validates_presence_of :match
|
||
delegate :key, :to => :lookup_key
|
||
validate :validate_range, :validate_list, :validate_regexp, :validate_match
|
||
before_validation :sanitize_match
|
||
before_validation :validate_and_cast_value
|
||
validate :validate_list, :validate_regexp
|
||
|
||
serialize :value
|
||
|
||
scope :default, :conditions => { :match => "default" }, :limit => 1
|
||
|
||
... | ... | |
value
|
||
end
|
||
|
||
private
|
||
def value_before_type_cast
|
||
return self.value if lookup_key.nil?
|
||
lookup_key.value_before_type_cast self.value
|
||
end
|
||
|
||
# TODO: ensures that the match contain only allowed path elements
|
||
def validate_match
|
||
def as_json(options={})
|
||
super({:only => [:value, :match, :lookup_key_id, :id]}.merge(options))
|
||
end
|
||
|
||
private
|
||
|
||
#TODO check multi match with matchers that have space (hostgroup = web servers,environment = production)
|
||
def sanitize_match
|
||
self.match = match.split(LookupKey::KEY_DELM).map {|s| s.split(LookupKey::EQ_DELM).map(&:strip).join(LookupKey::EQ_DELM)}.join(LookupKey::KEY_DELM) unless match.blank?
|
||
end
|
||
|
||
def validate_and_cast_value
|
||
return true if self.marked_for_destruction?
|
||
begin
|
||
self.value = lookup_key.cast_validate_value self.value
|
||
true
|
||
rescue
|
||
errors.add(:value, "is invalid #{lookup_key.key_type}")
|
||
false
|
||
end
|
||
end
|
||
|
||
def validate_regexp
|
||
return true unless (lookup_key.validator_type == 'regexp')
|
||
errors.add(:value, "is invalid") and return false unless (value =~ /#{lookup_key.validator_rule}/)
|
||
end
|
||
|
||
def validate_range
|
||
return true unless (lookup_key.validator_type == 'range')
|
||
errors.add(:value, "not within range #{lookup_key.validator_rule}") and return false unless eval(lookup_key.validator_rule).include?(value)
|
||
end
|
||
|
||
def validate_list
|
||
return true unless (lookup_key.validator_type == 'list')
|
||
errors.add(:value, "not in list") and return false unless lookup_key.validator_rule.split(LookupKey::KEY_DELM).map(&:strip).include?(value)
|
||
end
|
||
|
||
def as_json(options={})
|
||
super({:only => [:value, :match, :lookup_key_id, :id]}.merge(options))
|
||
errors.add(:value, "#{value} is not one of #{lookup_key.validator_rule}") and return false unless lookup_key.validator_rule.split(LookupKey::KEY_DELM).map(&:strip).include?(value)
|
||
end
|
||
|
||
end
|
app/models/puppet_class_importer.rb | ||
---|---|---|
class PuppetClassImporter
|
||
|
||
def initialize args = { }
|
||
@foreman_classes = { }
|
||
@proxy_classes = { }
|
||
if args[:proxy]
|
||
@proxy = args[:proxy]
|
||
elsif args[:url]
|
||
@proxy = ProxyAPI::Puppet.new(:url => args[:url])
|
||
else
|
||
url = SmartProxy.puppet_proxies.first.try(:url)
|
||
raise "Can't find a valid Proxy with a Puppet feature" if url.blank?
|
||
@proxy = ProxyAPI::Puppet.new(:url => url)
|
||
end
|
||
end
|
||
|
||
# return changes hash, currently exists to keep compatibility with importer html
|
||
def changes
|
||
changes = { 'new' => { }, 'obsolete' => { }, 'updated' => { } }
|
||
|
||
actual_environments.each do |env|
|
||
new = new_classes_for(env)
|
||
old = removed_classes_for(env)
|
||
updated = updated_classes_for(env)
|
||
changes['new'][env] = new if new.any?
|
||
changes['obsolete'][env] = old if old.any?
|
||
changes['updated'][env] = updated if updated.any?
|
||
end
|
||
|
||
old_environments.each do |env|
|
||
changes['obsolete'][env] ||= []
|
||
changes['obsolete'][env] << "_destroy_" unless actual_environments.include?(env)
|
||
end
|
||
changes
|
||
end
|
||
|
||
# Update the environments and puppetclasses based upon the user's selection
|
||
# It does a best attempt and can fail to perform all operations due to the
|
||
# user requesting impossible selections. Repeat the operation if errors are
|
||
# shown, after fixing the request.
|
||
# +changed+ : Hash with two keys: :new and :obsolete.
|
||
# changed[:/new|obsolete/] is and Array of Strings
|
||
# Returns : Array of Strings containing all record errors
|
||
def obsolete_and_new changes = { }
|
||
return if changes.empty?
|
||
changes.values.map(&:keys).flatten.uniq.each do |env_name|
|
||
if changes['new'] and changes['new'][env_name].try(:any?) # we got new classes
|
||
add_classes_to_foreman(env_name, JSON.parse(changes['new'][env_name]))
|
||
end
|
||
if changes['obsolete'] and changes['obsolete'][env_name].try(:any?) # we need to remove classes
|
||
remove_classes_from_foreman(env_name, JSON.parse(changes['obsolete'][env_name]))
|
||
end
|
||
if changes['updated'] and changes['updated'][env_name].try(:any?) # we need to update classes
|
||
update_classes_in_foreman(env_name, JSON.parse(changes['updated'][env_name]))
|
||
end
|
||
end
|
||
[]
|
||
#rescue => e
|
||
# logger.error(e)
|
||
# [e.to_s]
|
||
end
|
||
|
||
def new_classes_for environment
|
||
old_classes = db_classes_name(environment)
|
||
HashWithIndifferentAccess[
|
||
actual_classes(environment).values.map do |actual_class|
|
||
[actual_class.to_s, { "new" => actual_class.parameters }] unless old_classes.include?(actual_class.to_s)
|
||
end.compact
|
||
]
|
||
end
|
||
|
||
def removed_classes_for environment
|
||
db_classes_name(environment) - actual_classes_name(environment)
|
||
end
|
||
|
||
def updated_classes_for environment
|
||
return [] unless db_environments.include?(environment) && actual_environments.include?(environment)
|
||
HashWithIndifferentAccess[
|
||
db_classes(environment).map do |db_class|
|
||
params = EnvironmentClass.all_parameters_for_class(db_class.id, find_or_create_env(environment).id).map(&:lookup_key)
|
||
compare_classes(environment, db_class.name, params)
|
||
end.compact
|
||
]
|
||
end
|
||
|
||
# This method check if the puppet class exists in this environment, and compare the class params.
|
||
# Changes in the params are categorized to new parameters, removed parameters and parameters with a new
|
||
# default value.
|
||
def compare_classes(environment, klass, db_params)
|
||
return [] unless (actual_class = actual_classes(environment)[klass])
|
||
actual_params = actual_class.parameters
|
||
db_param_names = db_params.map(&:to_s)
|
||
|
||
param_changes = { }
|
||
old = db_param_names - actual_params.keys
|
||
param_changes['obsolete'] = old if old.any?
|
||
new = actual_params.reject { |key, value| db_param_names.include?(key) }
|
||
param_changes['new'] = new if new.any?
|
||
updated = updated_classes(actual_params, db_params)
|
||
param_changes['updated'] = updated if updated.any?
|
||
[klass, param_changes] if param_changes.keys.any?
|
||
end
|
||
|
||
def updated_classes(actual_params, db_params)
|
||
updated = { }
|
||
db_params.map do |p|
|
||
param_name = p.to_s
|
||
if !p.override && actual_params.has_key?(param_name) && actual_params[param_name] != p.default_value
|
||
updated[param_name] = actual_params[param_name]
|
||
end
|
||
end
|
||
updated
|
||
end
|
||
|
||
|
||
def db_environments
|
||
@foreman_envs ||= (Environment.all.map(&:name) - ignored_environments)
|
||
end
|
||
|
||
def actual_environments
|
||
@proxy_envs ||= (proxy.environments.map(&:to_s) - ignored_environments)
|
||
end
|
||
|
||
def new_environments
|
||
actual_environments - db_environments
|
||
end
|
||
|
||
def old_environments
|
||
db_environments - actual_environments
|
||
end
|
||
|
||
def db_classes environment
|
||
return @foreman_classes[environment] if @foreman_classes[environment]
|
||
return [] unless (env = Environment.find_by_name(environment))
|
||
@foreman_classes[environment] = env.puppetclasses.includes(:lookup_keys, :class_params)
|
||
end
|
||
|
||
def db_classes_name environment
|
||
db_classes(environment).map(&:name)
|
||
end
|
||
|
||
def actual_classes environment
|
||
@proxy_classes[environment] ||= proxy.classes(environment).reject do |key, value|
|
||
ignored_classes.find { |filter| filter.is_a?(Regexp) && filter =~ key }
|
||
end
|
||
end
|
||
|
||
def actual_classes_name environment
|
||
actual_classes(environment).keys
|
||
end
|
||
|
||
private
|
||
attr_reader :proxy
|
||
|
||
def ignored_environments
|
||
ignored_file[:ignored] || []
|
||
end
|
||
|
||
def ignored_classes
|
||
ignored_file[:filters] || []
|
||
end
|
||
|
||
def ignored_file
|
||
return @ignored_file if @ignored_file
|
||
file = File.join(Rails.root.to_s, "config", "ignored_environments.yml")
|
||
@ignored_file = File.exist?(file) ? YAML.load_file(file) : { }
|
||
rescue => e
|
||
logger.warn "Failed to parse environment ignore file: #{e}"
|
||
@ignored_file = { }
|
||
end
|
||
|
||
def logger
|
||
@logger ||= Rails.logger
|
||
end
|
||
|
||
def load_classes_from_json blob
|
||
ActiveSupport::JSON.decode blob
|
||
end
|
||
|
||
def add_classes_to_foreman env_name, klasses
|
||
env = find_or_create_env env_name
|
||
new_classes = klasses.map { |k| Puppetclass.find_or_create_by_name(k[0]) }
|
||
|
||
new_classes.each do |new_class|
|
||
EnvironmentClass.create! :puppetclass_id => new_class.id, :environment_id => env.id
|
||
class_params = klasses[new_class.to_s]
|
||
add_new_parameter(env, new_class, class_params) if class_params.any?
|
||
end
|
||
end
|
||
|
||
def update_classes_in_foreman environment, klasses
|
||
env = find_or_create_env(environment)
|
||
db_classes = env.puppetclasses.where(:name => klasses.keys)
|
||
db_classes.each do |db_class|
|
||
changed_params = klasses[db_class.to_s]
|
||
# Add new parameters
|
||
add_new_parameter(env, db_class, changed_params) if changed_params["new"]
|
||
# Unbind old parameters
|
||
remove_parameter(env, db_class, changed_params) if changed_params["obsolete"]
|
||
# Update parameters (affects solely the default value)
|
||
update_parameter(db_class, changed_params) if changed_params["updated"]
|
||
end
|
||
end
|
||
|
||
def update_parameter(db_class, changed_params)
|
||
changed_params["updated"].each do |param_name, value|
|
||
key = db_class.class_params.find_by_key param_name
|
||
if key.override == false
|
||
key.default_value = value
|
||
key.save!
|
||
end
|
||
end
|
||
end
|
||
|
||
def remove_parameter(env, db_class, changed_params)
|
||
changed_params["obsolete"].each do |param_name, value|
|
||
key = db_class.class_params.find_by_key param_name
|
||
key_in_env = EnvironmentClass.key_in_environment(env, db_class, key)
|
||
|
||
if key && key_in_env && !key.override?
|
||
#detach
|
||
key_in_env.destroy
|
||
# destroy if the key is not in any environment.
|
||
key.destroy unless EnvironmentClass.is_in_any_environment(db_class, key)
|
||
end
|
||
end
|
||
end
|
||
|
||
def add_new_parameter(env, klass, changed_params)
|
||
changed_params["new"].map do |param_name, value|
|
||
param = find_or_create_puppet_class_param klass, param_name, value
|
||
EnvironmentClass.create! :puppetclass_id => klass.id, :environment_id => env.id, :lookup_key_id => param.id
|
||
end
|
||
end
|
||
|
||
def remove_classes_from_foreman env_name, klasses
|
||
env = find_or_create_env(env_name)
|
||
classes = find_existing_foreman_classes(klasses)
|
||
env.puppetclasses.delete classes
|
||
# remove all old classes from hosts
|
||
HostClass.joins(:host).where(:hosts => { :environment_id => env.id }, :puppetclass_id => classes).destroy_all
|
||
if klasses.include? '_destroy_'
|
||
# we can't guaranty that the env would be removed as it might have hosts attached to it.
|
||
env.destroy
|
||
end
|
||
# remove all klasses that have no environment now
|
||
classes.not_in_any_environment.destroy_all
|
||
end
|
||
|
||
def logger
|
||
@logger ||= Rails.logger
|
||
end
|
||
|
||
def find_existing_foreman_classes klasses = []
|
||
Puppetclass.where(:name => klasses)
|
||
end
|
||
|
||
def find_or_create_env env
|
||
Environment.where(:name => env).first || Environment.create!(:name => env)
|
||
end
|
||
|
||
def find_or_create_puppet_class_param klass, param_name, value
|
||
klass.class_params.where(:key => param_name).first ||
|
||
LookupKey.create!(:key => param_name, :is_param => true,
|
||
:required => value.nil?, :override => value.nil?, :default_value => value,
|
||
:key_type => Foreman::ImporterPuppetclass.suggest_key_type(value))
|
||
end
|
||
|
||
end
|
app/models/puppetclass.rb | ||
---|---|---|
class Puppetclass < ActiveRecord::Base
|
||
include Authorization
|
||
has_and_belongs_to_many :environments
|
||
has_many :environment_classes, :dependent => :destroy
|
||
has_many :environments, :through => :environment_classes, :uniq => true
|
||
has_and_belongs_to_many :operatingsystems
|
||
has_and_belongs_to_many :hostgroups
|
||
has_many :host_classes, :dependent => :destroy
|
||
... | ... | |
|
||
has_many :lookup_keys, :inverse_of => :puppetclass
|
||
accepts_nested_attributes_for :lookup_keys, :reject_if => lambda { |a| a[:key].blank? }, :allow_destroy => true
|
||
|
||
# param classes
|
||
has_many :class_params, :through => :environment_classes, :uniq => true,
|
||
:class_name => 'LookupKey', :source => :lookup_key, :conditions => 'environment_classes.lookup_key_id is NOT NULL'
|
||
accepts_nested_attributes_for :class_params, :reject_if => lambda { |a| a[:key].blank? }, :allow_destroy => true
|
||
validates_uniqueness_of :name
|
||
validates_presence_of :name
|
||
validates_associated :environments
|
||
validates_format_of :name, :with => /\A(\S+\s?)+\Z/, :message => "can't be blank or contain white spaces."
|
||
audited
|
||
|
||
... | ... | |
scoped_search :in => :environments, :on => :name, :complete_value => :true, :rename => "environment"
|
||
scoped_search :in => :hostgroups, :on => :name, :complete_value => :true, :rename => "hostgroup"
|
||
scoped_search :in => :hosts, :on => :name, :complete_value => :true, :rename => "host", :ext_method => :search_by_host, :only_explicit => true
|
||
scoped_search :in => :class_params, :on => :key, :complete_value => :true
|
||
|
||
scope :not_in_any_environment, includes(:environment_classes).where(:environment_classes => {:environment_id => nil})
|
||
|
||
def to_param
|
||
name
|
||
... | ... | |
name.gsub(module_name+"::","")
|
||
end
|
||
|
||
|
||
# Populates the rdoc tree with information about all the classes in your modules.
|
||
# Firstly, we prepare the modules tree
|
||
# Secondly we run puppetdoc over the modulespath and manifestdir for all environments
|
app/views/common/_puppetclasses_or_envs_changed.html.erb | ||
---|---|---|
<%= form_tag "/#{controller_path}/obsolete_and_new" do -%>
|
||
<fieldset>
|
||
<legend>Accept these environment changes found in puppet? </legend>
|
||
<table>
|
||
<th>Environment</th><th>Operation</th><th>Classes</th>
|
||
<% for kind in ["new", "obsolete"] -%>
|
||
<table class="table table-striped">
|
||
<th>Environment</th><th>Operation</th><th>Puppet Modules</th>
|
||
<% for kind in ["new", "obsolete", "updated"] -%>
|
||
<% unless (envs = @changed[kind]).empty? -%>
|
||
<% for env in envs.keys.sort -%>
|
||
<tr class="<%= cycle("even", "odd")-%>" >
|
||
<tr>
|
||
<td>
|
||
<%= check_box_tag "changed[#{kind}][#{env}]", @changed[kind][env].to_json, true %>
|
||
<%= env -%>
|
||
</td>
|
||
<td>
|
||
<%= kind == "new" ? "Add:" : "Remove:" -%>
|
||
<%= {"new" => "Add:", "obsolete" => "Remove:", "updated" => "Update:"}[kind] -%>
|
||
</td>
|
||
<% pcs = @changed[kind][env] -%>
|
||
<td>
|
||
<% pcs = @changed[kind][env] -%>
|
||
<%= class_update_text pcs, env %>
|
||
</td>
|
||
</tr>
|
||
... | ... | |
<%= link_to "Cancel","/" + controller_path, :class => "btn" %>
|
||
<%= submit_tag "Update", :class => "btn btn-primary" %>
|
||
</div>
|
||
<% end -%>
|
||
<% end -%>
|
app/views/lookup_keys/_fields.html.erb | ||
---|---|---|
|
||
<div <%= "id=#{(f.object.key || 'new_lookup_keys').to_s.gsub(' ','_')} class='tab-pane fields' " %> >
|
||
<% is_param = f.object.is_param -%>
|
||
|
||
<%= text_f(f, :environment_classes, :value => f.object.environment_classes.map(&:environment).to_sentence, :label=> 'Puppet Environments', :disabled=>true) if is_param%>
|
||
|
||
<%= remove_child_link "Remove #{f.object.new_record? ? "Variable" : f.object}", f , {:class => 'btn btn-danger hide'} unless controller_name == "lookup_keys" %>
|
||
<%= text_f f, :key, :label => "Name" %>
|
||
<%= text_f f, :description %>
|
||
<%= text_f f, :default_value, :class => "span6", :help_inline => popover("?","value to use when there is no match", :title => "Default Value").html_safe %>
|
||
<%= selectable_f f, :validator_type, options_for_select(LookupKey::VALIDATION_TYPES, f.object.validator_type),
|
||
{ :include_blank => "string"},
|
||
{ :label => "Type Validator", :class => "medium",
|
||
:help_inline => popover("?","<dl> <dt>String</dt> <dd>everything goes</dd> <dt>Regexp</dt> <dd>regular expression to verify the value</dd>
|
||
<dt>List</dt> <dd>comma seperated values, e.g. 80,443<dd> </dt>", :title => "How values are validated").html_safe}
|
||
%>
|
||
<%= text_f f, :validator_rule, :label => "Validator constraint", :class => "span6",
|
||
:help_inline => popover("?","Values to match Type validator, leave blank if its String", :title => "Validator constraint")%>
|
||
<%= textarea_f f, :path, :rows => "4", :value => f.object.path, :label => "Order",
|
||
:help_inline => popover("?", "The order in which matchers keys are processed, first match wins.<br>
|
||
<%= text_f f, :key, :label => "Name", :disabled => f.object.is_param %>
|
||
<%= f.hidden_field :key if f.object.is_param %>
|
||
<%= textarea_f f, :description, :rows => :auto, :class=> "span6" %>
|
||
|
||
<%= show_puppet_class f %>
|
||
<%= checkbox_f(f, :override, :label => "Override", :onchange=>'toggleOverrideValue(this)', :disabled => f.object.required,
|
||
:help_inline => popover("?", "Whether the smart-variable should override the Puppet class default value.",
|
||
:title => "Override Value")) if is_param%>
|
||
<%= param_type_selector f %>
|
||
<%= textarea_f f, :default_value, :value => f.object.default_value_before_type_cast , :disabled => (f.object.is_param && !f.object.override), :class => "span6", :rows => :auto, :help_inline => popover("?","value to use when there is no match", :title => "Default Value") %>
|
||
<div <%= "id=#{(f.object.key || 'new_lookup_keys').to_s.gsub(' ','_')}_lookup_key_override_value" %> <%= "style='display:none;'" if (f.object.is_param && !f.object.override) %>>
|
||
<legend>Optional Input Validator</legend>
|
||
|
||
<%= checkbox_f(f, :required, :onchange=>'toggleMandatory(this)', :disabled => !f.object.override,
|
||
:help_inline => popover("?", "If checked, will raise an error if there is no default value and no matcher provide a value.",
|
||
:title => "Required Parameter")) if is_param %>
|
||
|
||
<%= validator_type_selector f %>
|
||
<%= text_f f, :validator_rule %>
|
||
<legend>Override Value For Specific Hosts</legend>
|
||
<%= textarea_f f, :path, :rows => :auto, :label => "Order", :value => f.object.path,
|
||
:help_inline => popover("?", "The order in which matchers keys are processed, first match wins.<br>
|
||
You may use multiple attributes as a matcher key, for example, an order of <code>hostgroup, environment</code>
|
||
would expect a matcher such as <code>hostgroup = \"web servers\", environment = production</code>", :title => "The order in which values are resolved").html_safe
|
||
%>
|
||
%>
|
||
|
||
<%# the following field is required to see child validations %>
|
||
<%= f.hidden_field :updated_at, :value => Time.now.to_i %>
|
||
<div class="children_fields">
|
||
<%= new_child_fields_template(f, :lookup_values, {:partial => "lookup_keys/value"}) %>
|
||
<%= f.fields_for :lookup_values do |lookup_values| %>
|
||
<%= render 'lookup_keys/value', :f => lookup_values %>
|
||
<% end %>
|
||
<%= add_child_link "+ Add Matcher-Value", :lookup_values, { :title => 'add a new matcher-value pair'} %>
|
||
<%# the following field is required to see child validations %>
|
||
<%= f.hidden_field :updated_at, :value => Time.now.to_i %>
|
||
<div class="children_fields">
|
||
<%= new_child_fields_template(f, :lookup_values, {:partial => "lookup_keys/value"}) %>
|
||
<%= f.fields_for :lookup_values do |lookup_values| %>
|
||
<%= render 'lookup_keys/value', :f => lookup_values %>
|
||
<% end %>
|
||
<%= add_child_link "+ Add Matcher-Value", :lookup_values, { :title => 'add a new matcher-value pair'} %>
|
||
</div>
|
||
</div>
|
||
</div>
|
app/views/lookup_keys/_value.html.erb | ||
---|---|---|
<%= text_f f, :match, :class => "span6",
|
||
:help_inline => popover("?", "Matcher is a combination of an attribute and its value, if they match, the value below would be provided.<br> You may use any attribute foreman knows about, such as facts etc for example: <code> domain = example.com </code> or <code> is_virtual = true</code>", :title => "Matcher")
|
||
%>
|
||
<%= text_f f, :value, :class => "span6", :help_inline => remove_child_link(icon_text("remove"), f, {:title => 'remove value'}) %>
|
||
<%= text_f f, :value, :value => f.object.value_before_type_cast, :class => "span6", :help_inline => remove_child_link(icon_text("remove"), f, {:title => 'remove value'}) %>
|
||
</div>
|
app/views/lookup_keys/index.html.erb | ||
---|---|---|
<% @lookup_keys.each do |lookup_key| %>
|
||
<tr class="<%= cycle("even", "odd") -%>">
|
||
<td><%= link_to_if_authorized h(lookup_key.key), hash_for_edit_lookup_key_path(:id => lookup_key) %></td>
|
||
<td><%= link_to_if_authorized h(lookup_key.puppetclass), hash_for_edit_puppetclass_path(:id => lookup_key.puppetclass) if lookup_key.puppetclass %></td>
|
||
|
||
<td>
|
||
<% puppet_class = lookup_key.param_class %>
|
||
<%= link_to_if_authorized h(puppet_class), hash_for_edit_puppetclass_path(:id => puppet_class) if puppet_class %></td>
|
||
<td><%=h lookup_key.lookup_values.count %></td>
|
||
<td><%= display_delete_if_authorized hash_for_lookup_key_path(:id => lookup_key), :confirm => "Delete #{lookup_key.key}?" %></td>
|
||
</tr>
|
||
<% end %>
|
||
</table>
|
||
<%= page_entries_info @lookup_keys, :model => "Smart Variables"%>
|
||
<%= will_paginate @lookup_keys %>
|
app/views/puppetclasses/_form.html.erb | ||
---|---|---|
<%= base_errors_for @puppetclass %>
|
||
<ul class="nav nav-tabs" data-tabs="tabs">
|
||
<li class="active"><a href="#primary" data-toggle="tab">Puppet Class</a></li>
|
||
<li><a href="#smart_class_param" data-toggle="tab">Smart Class Parameter</a></li>
|
||
<li><a href="#smart_vars" data-toggle="tab">Smart Variables</a></li>
|
||
</ul>
|
||
|
||
... | ... | |
|
||
<div class="tab-pane active" id="primary">
|
||
<%= text_f f, :name %>
|
||
|
||
<%= multiple_checkboxes f, :environments, @puppetclass, Environment, :label => "Puppet Environments" %>
|
||
<%= text_f f, :environments, :value => @puppetclass.environments.to_sentence, :label=> 'Puppet Environments', :disabled => true %>
|
||
<%= multiple_checkboxes f, :hostgroups, @puppetclass, Hostgroup, :label => "Hostgroups" %>
|
||
</div>
|
||
|
||
<div class="tab-pane" id="smart_class_param">
|
||
<% if @puppetclass.class_params.empty? -%>
|
||
<div class="alert alert-message alert-success">
|
||
<a class="close" href="#" data-dismiss="alert">×</a>
|
||
<p><strong>This Puppet class has no parameters in its signature.</strong><br>
|
||
To update the class signature, go to the Puppet Classes page and select 'Import'.</p>
|
||
</div>
|
||
<% else -%>
|
||
<div class="control-group">
|
||
<label class="control-label">Filter By Environment</label>
|
||
<div class="controls">
|
||
<%= select_tag "environment_filter", options_from_collection_for_select(@puppetclass.environments, "id", "name"),
|
||
:include_blank => "All - (Not filtered)", :onchange=>'filterByEnvironment(this)'%>
|
||
</div>
|
||
</div>
|
||
<div class="tabbable tabs-left">
|
||
<ul class="nav nav-tabs smart-var-tabs span2" data-tabs="pills">
|
||
<% @puppetclass.class_params.each do |key| -%>
|
||
<li data-used-environments=<%= key.environments.map(&:to_s).to_json %>><a data-toggle="tab" id="pill_<%= key.to_s.gsub(' ','_') %>" href="#<%= key.to_s.gsub(' ','_') %>"><%= key %><span class="label label-important fr">×</span></a></li>
|
||
<% end -%>
|
||
</ul>
|
||
|
||
<div class="tab-content span9 smart-var-content">
|
||
<%= f.fields_for :class_params do |lookup_keys_form| %>
|
||
<%= render 'lookup_keys/fields', :f => lookup_keys_form %>
|
||
<% end %>
|
||
</div>
|
||
</div>
|
||
<% end -%>
|
||
</div>
|
||
<div class="tab-pane" id="smart_vars">
|
||
<% if @puppetclass.lookup_keys.empty? -%>
|
||
<div class="alert alert-message alert-warning">
|
app/views/puppetclasses/index.html.erb | ||
---|---|---|
<tr class="<%= cycle("even", "odd") -%>">
|
||
<td><%=link_to_if_authorized h(puppetclass.name), hash_for_edit_puppetclass_path(:id => puppetclass) %></td>
|
||
<td>
|
||
<% for environment in puppetclass.environments -%>
|
||
<% puppetclass.environments.uniq.each do |environment| -%>
|
||
<%= link_to_function environment, 'show_rdoc(this)', :'data-url' => rdoc_classes_path(environment, puppetclass.name) %>
|
||
<% end %>
|
||
</td>
|
||
<td><%= puppetclass.hostgroups.map {|hg| link_to_if_authorized hg, hash_for_edit_hostgroup_path(:id=>hg)}.to_sentence.html_safe %></td>
|
||
<td> <%= link_to host_counter(puppetclass), hosts_path(:search => "class = #{puppetclass.name}")%></td>
|
||
<td><%= link_to @keys_counter[puppetclass.id] || 0, puppetclass_lookup_keys_path(puppetclass) %> </td>
|
||
<td><%= @keys_counter[puppetclass.name] || 0 %> </td>
|
||
<td>
|
||
<%= display_delete_if_authorized hash_for_puppetclass_path(:id => puppetclass), :confirm => "Delete #{puppetclass.name}?" %>
|
||
</td>
|
config/ignored_environments.yml.sample | ||
---|---|---|
#:ignored:
|
||
# - test
|
||
|
||
:filters:
|
||
- !ruby/regexp '/params$/'
|
||
- !ruby/regexp '/base$/'
|
||
- !ruby/regexp '/install$/'
|
||
- !ruby/regexp '/service$/'
|
||
- !ruby/regexp '/config$/'
|
config/routes.rb | ||
---|---|---|
resources :bookmarks, :except => [:show]
|
||
resources :lookup_keys, :except => [:new, :create] do
|
||
resources :lookup_values, :only => [:index, :create, :update, :destroy]
|
||
collection do
|
||
get 'auto_complete_search'
|
||
end
|
||
end
|
||
|
||
resources :facts, :only => [:index, :show] do
|
db/migrate/20120905095532_create_environment_classes.rb | ||
---|---|---|
class CreateEnvironmentClasses < ActiveRecord::Migration
|
||
class EnvironmentClass < ActiveRecord::Base; end
|
||
|
||
def self.up
|
||
rename_table :environments_puppetclasses, :environment_classes
|
||
add_column :environment_classes, :id, :primary_key
|
||
add_column :environment_classes, :lookup_key_id, :integer
|
||
end
|
||
|
||
def self.down
|
||
remove_column :environment_classes, :id
|
||
remove_column :environment_classes, :lookup_key_id
|
||
rename_table :environment_classes, :environments_puppetclasses
|
||
end
|
||
|
||
end
|
db/migrate/20120905131841_add_lookup_keys_override_and_required.rb | ||
---|---|---|
class AddLookupKeysOverrideAndRequired < ActiveRecord::Migration
|
||
def self.up
|
||
add_column :lookup_keys, :is_param, :boolean, :default => false
|
||
add_column :lookup_keys, :key_type, :string , :default => nil
|
||
add_column :lookup_keys, :override, :boolean, :default => false
|
||
add_column :lookup_keys, :required, :boolean, :default => false
|
||
end
|
||
|
||
def self.down
|
||
remove_column :lookup_keys, :is_param
|
||
remove_column :lookup_keys, :key_type
|
||
remove_column :lookup_keys, :override
|
||
remove_column :lookup_keys, :required
|
||
end
|
||
end
|
db/migrate/20120916080843_add_lookup_values_count_to_lookup_keys.rb | ||
---|---|---|
class AddLookupValuesCountToLookupKeys < ActiveRecord::Migration
|
||
def self.up
|
||
add_column :lookup_keys, :lookup_values_count, :integer, :default => 0
|
||
end
|
||
|
||
def self.down
|
||
remove_column :lookup_keys, :lookup_values_count
|
||
end
|
||
end
|
db/migrate/20120916080926_cache_lookup_values_count.rb | ||
---|---|---|
class CacheLookupValuesCount < ActiveRecord::Migration
|
||
def self.up
|
||
execute "update lookup_keys set lookup_values_count=(select count(*) from lookup_values where lookup_key_id=lookup_keys.id)"
|
||
end
|
||
|
||
def self.down
|
||
end
|
||
end
|
lib/foreman/controller/environments.rb | ||
---|---|---|
|
||
def import_environments
|
||
begin
|
||
@changed = Environment.importClasses params[:proxy]
|
||
opts = params[:proxy].blank? ? { } : { :url => SmartProxy.find(params[:proxy]).try(:url) }
|
||
@importer = PuppetClassImporter.new(opts)
|
||
@changed = @importer.changes
|
||
rescue => e
|
||
if e.message =~ /puppet feature/i
|
||
error "We did not find a foreman proxy that can provide the information, ensure that you have at least one Proxy with the puppet feature turned on."
|
||
... | ... | |
end
|
||
end
|
||
|
||
if @changed["new"].size > 0 or @changed["obsolete"].size > 0
|
||
if @changed["new"].size > 0 or @changed["obsolete"].size > 0 or @changed["updated"].size > 0
|
||
render "common/_puppetclasses_or_envs_changed"
|
||
else
|
||
notice "No changes to your environments detected"
|
||
... | ... | |
end
|
||
|
||
def obsolete_and_new
|
||
if (errors = ::Environment.obsolete_and_new(params[:changed])).empty?
|
||
if (errors = ::PuppetClassImporter.new.obsolete_and_new(params[:changed])).empty?
|
||
notice "Successfully updated environments and puppetclasses from the on-disk puppet installation"
|
||
else
|
||
error "Failed to update the environments and puppetclasses from the on-disk puppet installation<br/>" + errors.join("<br>")
|
||
... | ... | |
redirect_to :controller => controller_path
|
||
end
|
||
|
||
end
|
||
end
|
lib/foreman/default_settings/loader.rb | ||
---|---|---|
set('using_storeconfigs', "Foreman is sharing its database with Puppet Store configs", (!Puppet.settings.instance_variable_get(:@values)[:master][:dbadapter].empty? rescue false)),
|
||
set('Default_variables_Lookup_Path', "The Default path in which Foreman resolves host specific variables", ["fqdn", "hostgroup", "os", "domain"]),
|
||
set('Enable_Smart_Variables_in_ENC', "Should the smart variables be exposed via the ENC yaml output?", true),
|
||
set('Parametrized_Classes_in_ENC', "Should Foreman use the new format (2.6.5+) to answer Puppet in its ENC yaml output?", false),
|
||
set('enc_environment', "Should Foreman provide puppet environment in ENC yaml output? (this avoids the mismatch error between puppet.conf and ENC environment)", true),
|
||
set('use_uuid_for_certificates', "Should Foreman use random UUID's for certificate signing instead of hostnames", false),
|
||
set('update_environment_from_facts', "Should Foreman update a host's environment from its facts", false)
|
lib/foreman/importer_puppetclass.rb | ||
---|---|---|
class Foreman::ImporterPuppetclass
|
||
attr_reader :name, :module, :parameters
|
||
|
||
def initialize opts = { }
|
||
@name = opts["name"] || raise("must provide a puppet class name")
|
||
@module = opts["module"]
|
||
@parameters = opts["params"] || { }
|
||
end
|
||
|
||
def to_s
|
||
name and self.module ? "#{self.module}::#{name}" : name
|
||
end
|
||
|
||
# for now, equality is based on class name, and not on parameters
|
||
def ==(other)
|
||
name == other.name && self.module == other.module
|
||
end
|
||
|
||
def parameters?
|
||
@parameters.empty?
|
||
end
|
||
|
||
# Auto-detects the best validator type for the given (correctly typed) value.
|
||
# JSON and YAML are better undetected, to prevent the simplest strings to match.
|
||
def self.suggest_key_type value, default = nil, detect_json_or_yaml = false
|
||
case value
|
||
when String
|
||
begin
|
||
return "json" if JSON.load value
|
||
rescue
|
||
return "yaml" if YAML.load value
|
||
end if detect_json_or_yaml
|
||
"string"
|
||
when TrueClass, FalseClass
|
Also available in: Unified diff
fixes #832 - adds parameterized class support
Credits:
This patch adds the following featuresThis patch is based on the original work of Olivier Favre
<olivier@yakaz.com> many many thanks!
signature - each class can have a different set of parameters per environment.
users can add regexp or class names that the importer should ignore.
common usage case for this is classes such as ::config, ::install etc.
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 <ohadlevy@gmail.com>