Project

General

Profile

« Previous | Next » 

Revision 7db66baa

Added by Ori Rabin over 9 years ago

Fixes #3260- Allows puppet to manage value of smart class parameter that can be overrided

(cherry picked from commit 8e53b7c915ecab576e5950b20e75552ec6a02edb)

View differences:

app/assets/javascripts/lookup_keys.js
var type_field = fields.find("[id$='_key_type']");
var validator_type_field = fields.find("[id$='_validator_type']");
var default_value_field = fields.find("[id$='_default_value']");
var use_puppet_default = fields.find("[id$='use_puppet_default']");
var override_value_div = fields.find("[id$='lookup_key_override_value']");
var pill_icon = $('#pill_' + fields.attr('id') +' i');
mandatory.attr('disabled', override ? null : 'disabled');
type_field.attr('disabled', override ? null : 'disabled');
validator_type_field.attr('disabled', override ? null : 'disabled');
default_value_field.attr('disabled', override ? null : 'disabled' );
default_value_field.attr('disabled', override && !$(use_puppet_default).is(':checked') ? null : 'disabled' );
use_puppet_default.attr('disabled', override ? null : 'disabled' );
pill_icon.attr("class", override ? 'glyphicon glyphicon-flag' : "glyphicon- ");
override_value_div.toggle(override);
}
......
changeCheckboxEnabledStatus(avoidDuplicates, keyType == 'array' && item.checked);
}
function toggleUsePuppetDefaultValue(item, value_field) {
var use_puppet_default = $(item).is(':checked');
var fields = $(item).closest('.fields');
var value_field = fields.find('[id$=' + value_field + ']');
value_field.attr('disabled', use_puppet_default ? 'disabled' : null );
}
function filterByEnvironment(item){
if ($(item).val()=="") {
$('ul.smart-var-tabs li[data-used-environments] a').removeClass('text-muted');
app/controllers/api/v2/override_values_controller.rb
param :override_value, Hash, :required => true, :action_aware => true do
param :match, String
param :value, String
param :use_puppet_default, :bool
end
end
app/controllers/api/v2/smart_class_parameters_controller.rb
param :override, :bool
param :description, String
param :default_value, String
param :use_puppet_default, :bool
param :path, String
param :validator_type, String
param :validator_rule, String
app/models/lookup_key.rb
end
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
accepts_nested_attributes_for :lookup_values,
:reject_if => lambda { |a| a[:value].blank? && (a[:use_puppet_default].nil? || a[:use_puppet_default] == "0")},
:allow_destroy => true
before_validation :validate_and_cast_default_value
validates :key, :uniqueness => {:scope => :is_param }, :unless => Proc.new{|p| p.is_param?}
app/services/classification/base.rb
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)
lookup_values_for_key = all_lookup_values.where(:lookup_key_id => key.id, :use_puppet_default => false)
sorted_lookup_values = lookup_values_for_key.sort_by { |lv| key.path.index(lv.match.split(LookupKey::EQ_DELM).first) }
value = nil
if key.merge_overrides
......
def value_of_key(key, values)
value = if values[key.id] and values[key.id][key.to_s]
values[key.id][key.to_s][:value]
{:value => values[key.id][key.to_s][:value]}
else
key.default_value
{:value => key.default_value, :managed => key.use_puppet_default}
end
@safe_render.parse(value)
return nil if value[:managed]
@safe_render.parse(value[:value])
end
def hostgroup_matches
......
private
def update_generic_matcher(lookup_values, options)
if options[:skip_fqdn]
while lookup_values.present? && lookup_values.first.match.split(LookupKey::EQ_DELM).first == "fqdn"
lookup_values.delete_at(0)
end
end
if lookup_values.present?
lv = lookup_values.first
element, element_name = lv.match.split(LookupKey::EQ_DELM)
{:value => lv.value, :element => element,
:element_name => element_name}
computed_lookup_value = nil
lookup_values.each do |lookup_value|
element, element_name = lookup_value.match.split(LookupKey::EQ_DELM)
next if (options[:skip_fqdn] && element=="fqdn")
computed_lookup_value = {:value => lookup_value.value, :element => element,
:element_name => element_name}
break
end
computed_lookup_value
end
def update_array_matcher(should_avoid_duplicates, lookup_values, options)
......
end
end
{:value => values, :element => elements,
:element_name => element_names}
return nil unless values.present?
{:value => values, :element => elements, :element_name => element_names}
end
def update_hash_matcher(lookup_values, options)
......
values.deep_merge!(lookup_value.value)
end
{:value => values, :element => elements,
:element_name => element_names}
return nil unless values.present?
{:value => values, :element => elements, :element_name => element_names}
end
end
end
end
app/services/classification/class_param.rb
klasses[klass.name] ||= {}
if key_hash[klass.id]
key_hash[klass.id].each do |key|
klasses[klass.name][key.to_s] = value_of_key(key, values)
key_value = value_of_key(key, values)
klasses[klass.name][key.to_s] = key_value unless key_value.nil?
end
if klasses[klass.name] == {}
klasses[klass.name] = nil
end
else
klasses[klass.name] = nil
app/views/api/v2/override_values/base.json.rabl
object @override_value
attributes :id, :match, :value
attributes :id, :match, :value, :use_puppet_default
app/views/api/v2/smart_class_parameters/main.json.rabl
extends "api/v2/smart_class_parameters/base"
attributes :description, :override, :parameter_type, :default_value, :required, :validator_type, :validator_rule,
:merge_overrides, :avoid_duplicates, :override_value_order, :override_values_count, :created_at, :updated_at
attributes :description, :override, :parameter_type, :default_value, :use_puppet_default, :required, :validator_type, :validator_rule,
:merge_overrides, :avoid_duplicates, :override_value_order, :override_values_count, :created_at, :updated_at
app/views/common_parameters/_inherited_parameters.html.erb
<tr>
<th class='col-md-3'><%= _("Scope") %></th>
<th class='col-md-2'><%= _("Name") %></th>
<th class='col-md-7'><%= _("Value") %></th>
<th class='col-md-5'><%= _("Value") %></th>
<th class='col-md-2'><%= _("Use Puppet Default") %></th>
<th><%= _('Actions') %></th>
</tr>
</thead>
<tbody>
<% keys = inherited_parameters.keys.sort %>
<% keys.each do |name| %>
<tr>
<%= "<td class='col-md-3' rowspan='#{keys.size}'>#{_('Global')}</td>".html_safe if name == keys.first%>
<td class='col-md-2'><%= content_tag :span, name, :id=>"name_#{name}", :class => "col-md-2" %></td>
<td class='col-md-7'><%= parameter_value_field inherited_parameters[name] %></td>
<td>
<%= link_to_function(_("override"), "override_param(this)", :title => _("Override this value"),
:'data-tag' => 'override', :class =>"btn btn-default" ) if authorized_via_my_scope("host_editing", "create_params") %>
</tr>
<% if inherited_parameters.present? %>
<tbody>
<% keys = inherited_parameters.keys.sort %>
<% keys.each do |name| %>
<tr>
<%= "<td class='col-md-3' rowspan='#{keys.size}'>#{_('Global')}</td>".html_safe if name == keys.first %>
<td class='col-md-2'><%= content_tag :span, name, :id => "name_#{name}", :class => "col-md-2" %></td>
<td class='col-md-7'><%= parameter_value_field inherited_parameters[name] %></td>
<td>
<%= link_to_function(_("override"), "override_param(this)", :title => _("Override this value"),
:'data-tag' => 'override', :class => "btn btn-default") if authorized_via_my_scope("host_editing", "create_params") %>
</tr>
<% end %>
</tbody>
<% end %>
</tbody>
</table>
app/views/common_parameters/_puppetclass_parameter.html.erb
<td class="col-md-2">
<%= text_field_tag '', (f.object.lookup_key.key rescue ''), :class => 'form-control', :'data-property' => 'name', :disabled => true, :title => _('Parameter name') %>
</td>
<td class="col-md-7">
<%= f.text_area :value, :rows => line_count(f, :value), :class => 'form-control', :'data-property' => 'value', :disabled => (not authorized_via_my_scope("host_editing", "edit_params")), :placeholder => _("Value") %>
<td class="col-md-5">
<%= f.text_area :value, :rows => line_count(f, :value), :class => 'form-control', :'data-property' => 'value',
:disabled => (not authorized_via_my_scope("host_editing", "edit_params")) || f.object.use_puppet_default,
:placeholder => _("Value") %>
<%= text_field_tag '', (f.object.lookup_key.key_type rescue 'unknown'), :class=> 'hide', :'data-property' => 'type', :disabled => true, :title => _('Parameter type') %>
</td>
<td class="col-md-2">
<%= f.check_box :use_puppet_default, :'data-property' => 'use_puppet_default',
:disabled => (not authorized_via_my_scope("host_editing", "edit_params")),
:onchange=>'toggleUsePuppetDefaultValue(this, "value")' %>
<%= popover('', _('Do not send this parameter via the ENC. Puppet will use the value defined in the puppet manifest for this parameter')) %>
</td>
<td class="col-md-1">
<span class="help-block">
<%= link_to_remove_fields('', f) %>
app/views/hostgroups/_form.html.erb
<%= javascript 'host_edit', 'class_edit' %>
<%= javascript 'host_edit', 'class_edit', 'lookup_keys' %>
<%= form_for @hostgroup, :html => {:data => {:id => @hostgroup.try(:id)}} do |f| %>
<%= base_errors_for @hostgroup %>
......
<h6><%= _('Puppet classes parameters') %></h6>
<p/>
<%= render "puppetclasses/classes_parameters", { :obj => @hostgroup } %>
<p/>
<%= render "common_parameters/inherited_parameters" %>
<h6><%= _('Host group parameters') %></h6>
<p/>
<%= render "common_parameters/puppetclasses_parameters", :f => f %>
app/views/lookup_keys/_fields.html.erb
<%= show_puppet_class f %>
<%= checkbox_f(f, :override, :onchange => 'toggleOverrideValue(this)', :size => "col-md-8",
:help_block => _("Whether the smart-variable should override the Puppet class default value.")
:help_block => _('Whether the Smart Variable value is managed by Foreman')
) if is_param%>
<%= param_type_selector(f, :onchange => 'keyTypeChange(this)') %>
<%= textarea_f f, :default_value, :value => f.object.default_value_before_type_cast,:size => "col-md-8", :disabled => (f.object.is_param && !f.object.override), :rows => :auto,
:help_block => _("Value to use when there is no match") %>
<%= textarea_f f, :default_value, :value => f.object.default_value_before_type_cast,:size => "col-md-8",
:disabled => (f.object.is_param && (!f.object.override || f.object.use_puppet_default)),
:rows => :auto, :help_block => _("Value to use when there is no match") %>
<%= checkbox_f(f, :use_puppet_default, :size => "col-md-8",
:help_block => _('Do not send this parameter via the ENC. Puppet will use the value defined in the puppet manifest for this parameter'),
:onchange=>'toggleUsePuppetDefaultValue(this, "default_value")',
:disabled => (f.object.is_param && !f.object.override)) if is_param%>
<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, :size => "col-md-8", :disabled => !f.object.override,
......
<legend><%= _("Override value for specific hosts") %></legend>
<%= checkbox_f(f, :merge_overrides, :onchange => 'mergeOverridesChanged(this)',
:disabled => !f.object.supports_merge?, :size => "col-md-8",
:help_block => _("Should the matchers continue to look for matches after first find (only array/hash type).")) %>
:help_block => _("Should the matchers continue to look for matches after first find (only array/hash type). Note: merging overrides ignores all matchers that use puppet default")) %>
<%= checkbox_f(f, :avoid_duplicates, :disabled => (!f.object.supports_uniq? || !f.object.merge_overrides), :size => "col-md-8",
:help_block => _("Should the matched result avoid duplicate values (only array type).")) %>
<%= textarea_f f, :path, :rows => :auto, :label => _("Order"), :size => "col-md-8", :value => f.object.path,
......
%>
<div class="children_fields">
<%= new_child_fields_template(f, :lookup_values, {:partial => "lookup_keys/value"}) %>
<%= new_child_fields_template(f, :lookup_values, {:partial => "lookup_keys/value", :form_builder_attrs => {:is_param => is_param}}) %>
<%= f.fields_for :lookup_values do |lookup_values| %>
<%= render 'lookup_keys/value', :f => lookup_values %>
<%= render 'lookup_keys/value', :f => lookup_values, :is_param => is_param %>
<% end %>
<%= add_child_link '+ ' + _("Add Matcher-Value"), :lookup_values, { :title => _('add a new matcher-value pair')} %>
</div>
app/views/lookup_keys/_value.html.erb
:help_block => popover(_("Explain matchers"), _("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"))
%>
<%= textarea_f f, :value, :rows => :auto, :value => f.object.value_before_type_cast, :size => "col-md-8", :help_inline => remove_child_link(icon_text("remove"), f, {:title => _('remove value')}) %>
<%= checkbox_f(f, :use_puppet_default, :label => 'Use puppet default',
:help_inline => popover(_("Explain use puppet default"), _('Do not send this parameter via the ENC. Puppet will use the value defined in the puppet manifest for this parameter')),
:onchange=>'toggleUsePuppetDefaultValue(this, "value")') if is_param %>
<%= textarea_f f, :value, :rows => :auto, :value => f.object.value_before_type_cast, :size => "col-md-8",
:disabled => f.object.use_puppet_default,
:help_inline => remove_child_link(icon_text("remove"), f, {:title => _('remove value')}) %>
</div>
db/migrate/20140915141937_add_should_use_puppet_default_to_lookup_value_and_key.rb
class AddShouldUsePuppetDefaultToLookupValueAndKey < ActiveRecord::Migration
def up
add_column :lookup_values, :use_puppet_default, :boolean, :default => false
add_column :lookup_keys, :use_puppet_default, :boolean
end
def down
remove_column :lookup_values, :use_puppet_default
remove_column :lookup_keys, :use_puppet_default
end
end
test/factories/puppet_related.rb
end
after_create do |lkey,evaluator|
evaluator.overrides.each do |match,value|
FactoryGirl.create :lookup_value, :lookup_key_id => lkey.id, :value => value, :match => match
FactoryGirl.create :lookup_value, :lookup_key_id => lkey.id, :value => value, :match => match, :use_puppet_default => false
end
lkey.reload
end
......
end
end
end
trait :with_use_puppet_default do
use_puppet_default true
end
end
factory :lookup_value
test/unit/classification_test.rb
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)},location=#{taxonomies(:location1)}",
:value => 'test'
:value => 'test',
:use_puppet_default => false
end
enc = classification.enc
......
classparam.expects(:environment_id).returns(env.id)
classparam.expects(:puppetclass_ids).returns(Array.wrap(pc).map(&:id))
classparam.expects(:attr_to_value).with('comment').returns('override')
assert_equal({lkey.id => {lkey.key => {:value => 'overridden value', :element => 'comment', :element_name => 'override'}}}, classparam.send(:values_hash))
end
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => ['test']
:value => ['test'],
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => ['test']
:value => ['test'],
:use_puppet_default => false
end
enc = classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => ['test']
:value => ['test'],
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => ['test']
:value => ['test'],
:use_puppet_default => false
end
enc = classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => {:example => {:a => 'test'}}
:value => {:example => {:a => 'test'}},
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => {:example => {:b => 'test2'}}
:value => {:example => {:b => 'test2'}},
:use_puppet_default => false
end
enc = classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => {:example => 'test2'}
:value => {:example => 'test2'},
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => {:example => 'test'}
:value => {:example => 'test'},
:use_puppet_default => false
end
enc = classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => {:a => 'test'}
:value => {:a => 'test'},
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => {:example => {:b => 'test2'}}
:value => {:example => {:b => 'test2'}},
:use_puppet_default => false
end
value3 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "os=#{operatingsystems(:redhat)}",
:value => {:example => {:b => 'test3'}}
:value => {:example => {:b => 'test3'}},
:use_puppet_default => false
end
enc = classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => {:example => {:a => 'test'}}
:value => {:example => {:a => 'test'}},
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => {:example => {:b => 'test2'}}
:value => {:example => {:b => 'test2'}},
:use_puppet_default => false
end
value3 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "os=#{operatingsystems(:redhat)}",
:value => {:example => {:a => 'test3'}}
:value => {:example => {:a => 'test3'}},
:use_puppet_default => false
end
enc = classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => ['test']
:value => ['test'],
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => ['test']
:value => ['test'],
:use_puppet_default => false
end
enc = global_param_classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => ['test']
:value => ['test'],
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => ['test']
:value => ['test'],
:use_puppet_default => false
end
enc = global_param_classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => {:example => {:a => 'test'}}
:value => {:example => {:a => 'test'}},
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => {:example => {:b => 'test2'}}
:value => {:example => {:b => 'test2'}},
:use_puppet_default => false
end
enc = global_param_classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => {:a => 'test'}
:value => {:a => 'test'},
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => {:example => {:b => 'test2'}}
:value => {:example => {:b => 'test2'}},
:use_puppet_default => false
end
value3 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "os=#{operatingsystems(:redhat)}",
:value => {:example => {:b => 'test3'}}
:value => {:example => {:b => 'test3'}},
:use_puppet_default => false
end
enc = global_param_classification.enc
......
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => {:example => {:a => 'test'}}
:value => {:example => {:a => 'test'}},
:use_puppet_default => false
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => {:example => {:b => 'test2'}}
:value => {:example => {:b => 'test2'}},
:use_puppet_default => false
end
value3 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "os=#{operatingsystems(:redhat)}",
:value => {:example => {:a => 'test3'}}
:value => {:example => {:a => 'test3'}},
:use_puppet_default => false
end
enc = global_param_classification.enc
......
global_param_classification.send(:values_hash))
end
test "#enc should not return class parameters when default value should use puppet default" do
lkey = FactoryGirl.create(:lookup_key, :as_smart_class_param, :with_override, :with_use_puppet_default,
:puppetclass => puppetclasses(:one))
enc = classification.enc
assert enc['base'][lkey.key].nil?
end
test "#enc should not return class parameters when lookup_value should use puppet default" do
lkey = FactoryGirl.create(:lookup_key, :as_smart_class_param, :with_override, :with_use_puppet_default,
:puppetclass => puppetclasses(:one), :path => "location")
as_admin do
LookupValue.create! :lookup_key_id => lkey.id,
:match => "location=#{taxonomies(:location1)}",
:value => 'test',
:use_puppet_default => true
end
enc = classification.enc
assert enc['base'][lkey.key].nil?
end
test "#enc should return class parameters when default value and lookup_values should not use puppet default" do
lkey = FactoryGirl.create(:lookup_key, :as_smart_class_param, :with_override, :use_puppet_default => false,
:puppetclass => puppetclasses(:one), :path => "location")
lvalue = as_admin do
LookupValue.create! :lookup_key_id => lkey.id,
:match => "location=#{taxonomies(:location1)}",
:value => 'test',
:use_puppet_default => false
end
enc = classification.enc
assert_equal lvalue.value, enc['base'][lkey.key]
end
test "#enc should not return class parameters when merged lookup_values and default are all using puppet default" do
key = FactoryGirl.create(:lookup_key, :as_smart_class_param, :use_puppet_default => true,
:override => true, :key_type => 'hash', :merge_overrides => true,
:default_value => {}, :path => "organization\nos\nlocation",
:puppetclass => puppetclasses(:one))
value = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "location=#{taxonomies(:location1)}",
:value => {:example => {:a => 'test'}},
:use_puppet_default => true
end
value2 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "organization=#{taxonomies(:organization1)}",
:value => {:example => {:b => 'test2'}},
:use_puppet_default => true
end
value3 = as_admin do
LookupValue.create! :lookup_key_id => key.id,
:match => "os=#{operatingsystems(:redhat)}",
:value => {:example => {:a => 'test3'}},
:use_puppet_default => true
end
enc = classification.enc
assert enc['base'][key.key].nil?
end
private
attr_reader :classification

Also available in: Unified diff