Project

General

Profile

« Previous | Next » 

Revision b09b4515

Added by Ohad Levy about 14 years ago

  • ID b09b451583194c3bae7cafc2b5331f1319c4681a
Fixes #193 - move hosts controller away from AS
  • Remove active scaffold from hosts controller.
  • changed default host graphs from one day for up to one week
  • added dynamic dropdown boxes for host creation / edit page (e.g. for puppet classes, operating systems etc).
  • converted puppet class selection to a template, using it in hostgroups and hosts edit page.
  • Renamed puppetgroups to hostgroups for consistency
  • Added search option to list, this include search by facts as well

View differences:

app/controllers/application_controller.rb
def self.active_scaffold_controller_for(klass)
return FactNamesController if klass == Puppet::Rails::FactName
return FactValuesController if klass == Puppet::Rails::FactValue
return HostsController if klass == Puppet::Rails::Host
return "#{klass}ScaffoldController".constantize rescue super
end
app/controllers/hosts_controller.rb
before_filter :require_login, :except => [ :query, :externalNodes, :lookup ]
before_filter :require_ssl, :except => [ :query, :externalNodes, :lookup ]
before_filter :find_hosts, :only => :query
before_filter :ajax_methods, :only => [:environment_selected, :architecture_selected, :os_selected]
helper :hosts
active_scaffold :host do |config|
list.empty_field_text ='N/A'
list.per_page = 15
list.sorting = {:name => 'ASC' }
config.actions.exclude :show
config.list.columns = [:name, :operatingsystem, :environment, :last_report ]
config.columns = %w{ name hostgroup puppetclasses environment domain puppetmaster comment host_parameters}
config.columns[:hostgroup].form_ui = :select
config.columns[:domain].form_ui = :select
config.columns[:environment].form_ui = :select
config.columns[:puppetclasses].form_ui = :select
config.columns[:puppetclasses].options = { :draggable_lists => {}}
config.columns[:fact_values].association.reverse = :host
config.nested.add_link("Inventory", [:fact_values])
config.columns[:puppetmaster].description = "leave empty if it is #{SETTINGS[:puppet_server] || "puppet"}"
# do not show these fields if unattended mode is disabled
if SETTINGS[:unattended].nil? or SETTINGS[:unattended]
config.columns = %w{ name ip mac hostgroup puppetclasses operatingsystem environment architecture media domain model root_pass serial puppetmaster ptable disk comment host_parameters}
config.columns[:architecture].form_ui = :select
config.columns[:operatingsystem].form_ui = :select
config.columns[:operatingsystem].options = { :update_column => :media }
config.columns[:media].form_ui = :select
config.columns[:model].form_ui = :select
config.columns[:ptable].form_ui = :select
config.columns[:serial].description = "unsed for now"
config.columns[:disk].description = "the disk layout to use"
config.columns[:build].form_ui = :checkbox
config.action_links.add 'setBuild', :label => 'Build', :inline => false,
:type => :record, :confirm => "This actions recreates all needed settings for host installation, if the host is
already running, it will disable certain functions.\n
Are you sure you want to reinstall this host?"
end
config.action_links.add 'rrdreport', :label => 'RRDReport', :inline => true,
:type => :record, :position => :after if SETTINGS[:rrd_report_url]
config.action_links.add 'externalNodes', :label => 'YAML', :inline => true,
:type => :record, :position => :after
config.action_links.add 'puppetrun', :label => 'Run', :inline => true,
:type => :record, :position => :after if SETTINGS[:puppetrun]
def index
@search = Host.search(params[:search])
@hosts = @search.paginate :page => params[:page]
end
def show
# filter graph time range
@range = (params["range"].empty? ? 1 : params["range"].to_i)
@range = (params["range"].empty? ? 7 : params["range"].to_i)
range = @range.days.ago
@host = Host.find params[:id]
......
@report_summary = Report.summarise(range, @host)
end
def new
@host = Host.new
@host.host_parameters.build
end
def create
@host = Host.new(params[:host])
if @host.save
flash[:foreman_notice] = "Successfully created host."
redirect_to @host
else
render :action => 'new'
end
end
def edit
@host = Host.find(params[:id])
@environment = @host.environment
@architecture = @host.architecture
@operatingsystem = @host.operatingsystem
end
def update
@host = Host.find(params[:id])
if @host.update_attributes(params[:host])
flash[:foreman_notice] = "Successfully updated host."
redirect_to @host
else
render :action => 'edit'
end
end
def destroy
@host = Host.find(params[:id])
if @host.destroy
flash[:foreman_notice] = "Successfully destroyed host."
else
flash[:foreman_error] = @host.errors.full_messages.join("<br>")
end
redirect_to hosts_url
end
# form AJAX methods
def environment_selected
if params[:environment_id].to_i > 0 and @environment = Environment.find(params[:environment_id])
render :partial => 'puppetclasses/class_selection', :locals => {:obj => (@host ||= Host.new)}
else
return head(:not_found)
end
end
def architecture_selected
assign_parameter "architecture"
end
def os_selected
assign_parameter "operatingsystem"
end
# list AJAX methods
def fact_selected
@fact_name_id = params[:search_fact_values_fact_name_id_eq].to_i
@fact_values = FactValue.find(:all, :select => 'DISTINCT value', :conditions => {
:fact_name_id => @fact_name_id }, :order => 'value ASC') if @fact_name_id > 0
render :partial => 'fact_selected', :layout => false
end
#returns a yaml file ready to use for puppet external nodes script
#expected a fqdn parameter to provide hostname to lookup
#see example script in extras directory
......
def query
respond_to do |format|
format.html
format.yml { render :text => @hosts.to_yaml }
format.html
format.yml { render :text => @hosts.to_yaml }
end
end
......
if fact.empty? and klass.empty?
render :text => '404 Not Found', :status => 404 and return
end
@hosts = []
counter = 0
......
render :text => '404 Not Found', :status => 404 and return if @hosts.empty?
end
def ajax_methods
return head(:method_not_allowed) unless request.xhr?
@host = Host.find(params[:id]) unless params[:id].empty?
end
def assign_parameter name
if params["#{name}_id"].to_i > 0 and eval("@#{name} = #{name.capitalize}.find(params['#{name}_id'])")
render :partial => name, :locals => {:host => (@host ||= Host.new)}
else
return head(:not_found)
end
end
end
app/helpers/hosts_helper.rb
link_to_if(Report.maximum('id', :conditions => {:host_id => record.id}), time, report_host_path(record))
end
def root_pass_form_column(record, field_name)
password_field_tag field_name, record.root_pass
end
# method that reformats the hostname column by adding the status icons
def name_column(record)
if record.build and not record.installed_at.nil?
......
link_to(record.shortname, host_path(record))
end
def disk_form_column(record, field_name)
text_area_tag field_name, record.disk, :cols => 120, :rows => 10
end
def comment_form_column(record, field_name)
text_area_tag field_name, record.comment, :cols => 120, :rows => 10
end
def options_for_association_conditions(association)
case association.name
when :media
{'medias.operatingsystem_id' => @record.operatingsystem_id}
when :ptable
{'ptables.operatingsystem_id' => @record.operatingsystem_id}
else
super
end
end
def days_ago time
((Time.now - time) / 1.day).round.to_i
end
def searching?
params[:search].empty?
end
end
app/models/domain.rb
:allow_blank => true, :allow_nil => true
validates_presence_of :name
default_scope :order => 'name'
before_destroy Ensure_not_used_by.new(:hosts, :subnets)
def to_label
app/models/environment.rb
validates_presence_of :name
validates_uniqueness_of :name
validates_format_of :name, :with => /^\S+$/, :message => "Name cannot contain spaces"
default_scope :order => 'name'
def to_label
name
app/models/host.rb
belongs_to :hostgroup
has_many :reports, :dependent => :destroy
has_many :host_parameters, :dependent => :destroy
accepts_nested_attributes_for :host_parameters, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
named_scope :recent, lambda { |*args| {:conditions => ["last_report > ?", (args.first || (SETTINGS[:run_interval] + 5.minutes).ago)]} }
named_scope :out_of_sync, lambda { |*args| {:conditions => ["last_report < ?", (args.first || (SETTINGS[:run_interval] + 5.minutes).ago)]} }
......
# returns the list of puppetclasses a host is in.
def puppetclasses_names
if hostgroup.nil?
return puppetclasses.collect {|c| c.name}
else
return (hostgroup.puppetclasses.collect {|c| c.name} + puppetclasses.collect {|c| c.name}).uniq
end
return all_puppetclasses.collect {|c| c.name}
end
def all_puppetclasses
return hostgroup.nil? ? puppetclasses : (hostgroup.puppetclasses + puppetclasses).uniq
end
# provide information about each node, mainly used for puppet external nodes
# TODO: remove hard coded default parameters into some selectable values in the database.
......
param = {}
# maybe these should be moved to the common parameters, leaving them in for now
param["puppetmaster"] = puppetmaster
param["domainname"] = domain.fullname unless domain.fullname.empty?
param["domainname"] = domain.fullname unless domain.nil? or domain.fullname.nil?
param.update self.params
return Hash['classes' => self.puppetclasses_names, 'parameters' => param, 'environment' => environment.to_s]
end
......
# read common parameters
CommonParameter.find_each {|p| parameters.update Hash[p.name => p.value] }
# read domain parameters
domain.domain_parameters.each {|p| parameters.update Hash[p.name => p.value] }
domain.domain_parameters.each {|p| parameters.update Hash[p.name => p.value] } unless domain.nil?
# read group parameters only if a host belongs to a group
hostgroup.group_parameters.each {|p| parameters.update Hash[p.name => p.value] } unless hostgroup.nil?
# and now read host parameters, override if required
app/models/hostgroup.rb
accepts_nested_attributes_for :group_parameters, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
has_many :hosts
before_destroy Ensure_not_used_by.new(:hosts)
default_scope :order => 'name'
acts_as_audited
......
def to_s
name
end
def all_puppetclasses
puppetclasses
end
def hostgroup
self
end
end
app/models/model.rb
before_destroy Ensure_not_used_by.new(:hosts)
validates_uniqueness_of :name
validates_presence_of :name
default_scope :order => 'name'
def to_label
name
app/views/home/settings.erb
<div id="settings">
<ul>
<li><%= link_to 'Puppet Classes', puppetclasses_path %> </li>
<li><%= link_to 'Puppet Groups', hostgroups_path %> </li>
<li><%= link_to 'Host Groups', hostgroups_path %> </li>
<li><%= link_to 'Domains', domains_path %> </li>
<li><%= link_to 'Operating Systems', operatingsystems_path %> </li>
<li><%= link_to 'Installation Medias',medias_path %> </li>
app/views/hostgroups/_form.html.erb
<%= f.text_field :name %>
</p>
<table>
<th>Included services</th>
<th colspan=3>Available Services</th>
<tr>
<td id=hostclasses>
<div id=selected_classes>
<%# hidden field to ensure that classes gets removed if none are defined -%>
<%= hidden_field_tag "hostgroup[puppetclass_ids][]" %>
<%= render :partial => "puppetclasses/selectedClasses",
:collection => @hostgroup.puppetclasses,:as => :klass,
:locals => { :type => Hostgroup } %>
</div>
</td>
<%= render :partial => "puppetclasses/classes", :locals => {:puppetclasses => Puppetclass.all - @hostgroup.puppetclasses, :type => Hostgroup } %>
</tr>
</table>
<%= render 'puppetclasses/class_selection', :obj => @hostgroup %>
<%= render :partial => "common_parameters/parameters", :locals => { :f => f, :type => :group_parameters } %>
app/views/hosts/_architecture.html.erb
<%= label :host, :operatingsystem, "Operating system" %>
<%= collection_select :host, :operatingsystem_id, @architecture.operatingsystems, :id, :to_label, :include_blank => true %>
<span id=os_sub_select>
<% if @operatingsystem -%>
<%= render :partial => 'operatingsystem' %>
<% end -%>
</span>
<%= observe_field :host_operatingsystem_id,
:url => { :action => :os_selected, :id => @host.id },
:update => :os_sub_select,
:with => :operatingsystem_id %>
app/views/hosts/_fact_selected.html.erb
<span id=fact_search>
<%= label_tag "Fact Name" %>
<%= collection_select :search, :fact_values_fact_name_id_eq,
Puppet::Rails::FactName.find(:all, :order => "name ASC"),
:id, :name, :selected => @fact_name_id, :include_blank => true
%>
<% if @fact_values %>
<%= label :fact_values_value_eq, "Value" %>
<%= collection_select :search, :fact_values_value_eq, @fact_values, :value, :value %>
<% end %>
</span>
<%= observe_field(:search_fact_values_fact_name_id_eq,
:url => { :action => :fact_selected },
:update => :fact_search,
:with => :search_fact_values_fact_name_id_eq) %>
app/views/hosts/_form.html.erb
<% form_for @host do |f| %>
<%= f.error_messages %>
<% field_set_tag 'Primary settings' do -%>
<%= f.label :name %>
<%= f.text_field :name, :size => 16, :value => @host.shortname %>
<%= f.label :hostgroup %>
<%= f.collection_select :hostgroup_id, Hostgroup.all, :id, :name, :include_blank => true %>
<%= f.label :environment %>
<%= f.collection_select :environment_id, Environment.all, :id, :to_label, :include_blank => true %>
<%= f.label :puppetmaster %>
<%= f.text_field :puppetmaster, :size => 10, :value => @host.puppetmaster %>
<%= link_to_function "Additional Classes", toggle_div(:classlist) %>
<div id="classlist", style=display:none;>
<% if @environment -%>
<%= render 'puppetclasses/class_selection', :obj => @host %>
<% else -%>
<b>Please select an Environment first</b>
<% end -%>
</div>
<% end -%>
<%= observe_field :host_environment_id,
:url => { :action => :environment_selected, :id => @host.id },
:update => :classlist,
:with => :environment_id %>
<% if SETTINGS[:unattended].nil? or SETTINGS[:unattended] -%>
<%= render 'unattended', :f => f %>
<% end -%>
<% field_set_tag 'Additional Information' do -%>
<%= f.text_area :comment, :size => "120x5" %>
<% end -%>
<%= render :partial => "common_parameters/parameters", :locals => {:f => f, :type => :host_parameters} %>
<p><%= f.submit "Submit" %></p>
<% end %>
app/views/hosts/_minilist.html.erb
<div>
<div id="hostlist">
<% if hosts.size > 0 -%>
<table class="list">
<caption> <%= header -%> </caption>
<caption> <%= header ||= "" -%> </caption>
<tr>
<th>Name</th><th>Operatingsystem</th><th>Environment</th><th>Last Report</th><th>Edit</th>
<th><%= order @search, :by => :name %></th>
<th>Operating system</th>
<th>Environment</th>
<th>Model</th>
<th>Host Group</th>
<th><%= order @search, :by => :last_report %></th>
<th>Edit</th>
<th>Delete</th>
</tr>
<% hosts.each do |host| -%>
<tr class="<%= cycle("even", "odd") -%>">
<td><%=name_column(host) %></td>
<td><%=h host.try(:os) %></td>
<td><%=h host.try(:environment) %></td>
<td><%=h host.try(:model) %></td>
<td><%=h host.try(:hostgroup) %></td>
<td><%=last_report_column(host) %></td>
<td><%= link_to 'Edit', edit_host_url(host) %></td>
<td><%= link_to "Destroy", host, :confirm => 'Are you sure?', :method => :delete %></td>
<% end -%>
</tr>
</table>
app/views/hosts/_operatingsystem.html.erb
<%= label :host, :media %>
<%= collection_select :host, :media_id, @operatingsystem.medias, :id, :to_label %>
<%= label :host, :ptable, "Partition Table" %>
<%= collection_select :host, :ptable_id, @operatingsystem.ptables, :id, :to_label %>
app/views/hosts/_unattended.html.erb
<% field_set_tag 'Unattended settings', :id => "unattended" do -%>
<% field_set_tag 'Network settings', :id => "network" do -%>
<%= f.label :domain %>
<%= f.collection_select :domain_id, Domain.all, :id, :to_label %>
<%= f.label :ip %>
<%= f.text_field :ip, :size => 16 %>
<%= f.label :mac %>
<%= f.text_field :mac, :size => 17 %>
<% end -%>
<% field_set_tag 'Operating system settings', :id => "operatingsystem" do -%>
<p><span id="architecture">
<%= f.label :architecture %>
<%= f.collection_select :architecture_id, Architecture.all, :id, :to_label, :include_blank => true %>
<span id="host_os">
<% if @architecture -%>
<%= render "architecture" -%>
<% end -%>
</span>
</span></p>
<%= f.label :root_pass %>
<%= f.password_field :root_pass %>
<%= f.label :model %>
<%= f.collection_select :model_id, Model.all, :id, :to_label, :include_blank => true %>
<%= f.label :serial %>
<%= f.select :serial, ["","0,9600n8","0,19200n8","1,9600n8","1,19200n8"] %>
<p>
<%= link_to_function "Switch to custom disk layout", toggle_div("custom_disk") %>
<div id="custom_disk", style=display:none;>
<%= f.label :disk, "Custom Disk layout" %>
<%= f.text_area :disk, :size => "50x10", :disabled => true %>
</div>
<% end -%>
<% end -%>
<%= observe_field :host_architecture_id,
:url => { :action => :architecture_selected, :id => @host.id },
:update => :host_os,
:with => :architecture_id %>
app/views/hosts/edit.html.erb
<% title "Edit Host" %>
<%= render :partial => 'form' %>
<p>
<%= link_to "Show", @host %> |
<%= link_to "View All", hosts_path %>
</p>
app/views/hosts/index.html.erb
<%= link_to_function "Search", toggle_div(:search) %>
<%= link_to "New Host", new_host_path %>
<div id="search" style="display: <%= searching? ? 'none':'inline' %>;">
<% form_for @search do |f|-%>
<%= f.label :name_like, "Name" %>
<%= f.text_field :name_like, :size => 15 %>
<%= f.label :environment_id_equals, "Environment" %>
<%= f.collection_select :environment_id_equals, Environment.all, :id, :name, :include_blank => true %>
<%= f.label :hostgroup, "Role" %>
<%= f.collection_select :hostgroup_id_eq, Hostgroup.all, :id, :name, :include_blank => true %>
<%= render :partial => 'fact_selected' %>
<p>
<%= f.submit "Search" %>
<% end %>
</div>
<%= render 'minilist', :hosts => @hosts, :header => "Hosts" %>
app/views/hosts/new.html.erb
<% title "New Host" %>
<%= render :partial => 'form' %>
<p><%= link_to "Back to List", hosts_path %></p>
app/views/hosts/show.html.erb
<%= link_to "Reports", reports_host_path(@host) %>
<%= link_to "YAML", :action=>"externalNodes", :id => @host %>
<%= link_to_if @host.build == false, "Build", {:action=>"setBuild", :id => @host} %>
<%= link_to_if SETTINGS[:puppetrun], "Puppet Run", {:action=>"puppetrun", :id => @host} %>
<%= link_to_if SETTINGS[:puppetrun], "Run Puppet", {:action=>"puppetrun", :id => @host} %>
<%= link_to "Delete", host_path(@host), :confirm => 'Are you sure?', :method => :delete%>
</div>
app/views/puppetclasses/_class_selection.html.erb
<% field_set_tag 'Puppet classes' do -%>
<table>
<th>Included classes</th>
<th colspan=3>Available classes</th>
<tr>
<td id=hostclasses>
<div id=selected_classes>
<%# hidden field to ensure that classes gets removed if none are defined -%>
<%= hidden_field_tag obj.class.to_s.downcase + "[puppetclass_ids][]" %>
<%= render :partial => "puppetclasses/selectedClasses",
:collection => obj.puppetclasses ,:as => :klass,
:locals => { :type => obj.class.to_s.downcase } %>
</div>
<% if (klasses = obj.hostgroup.try(:puppetclasses)).is_a?(Array) and obj.is_a?(Host) -%>
<% for klass in klasses -%>
<li><%= h klass.name %></li>
<% end -%>
<% end -%>
</td>
<%= render :partial => "puppetclasses/classes", :locals =>
{:puppetclasses => (obj.is_a?(Host) ? @environment.puppetclasses : Puppetclass.all) - obj.all_puppetclasses, :type => obj.class.to_s.downcase } %>
</tr>
</table>
<% end -%>
app/views/puppetclasses/_selectedClasses.html.erb
<% content_tag_for :div, klass, :selected do %>
<li><%= link_to_remove_puppetclass klass %></li>
<%= hidden_field_tag "#{type.to_s.downcase}[puppetclass_ids][]", klass.id %>
<%= hidden_field_tag "#{type}[puppetclass_ids][]", klass.id %>
<% end -%>
config/routes.rb
:requirements => { :name => /[^\.][\w\.-]+/ }
map.connect "/hosts/query", :controller => 'hosts', :action => 'query'
map.resources :hosts,
:member => {:report => :get, :reports => :get, :facts => :get},
:collection => { :show_search => :get},
:active_scaffold => true
:member => {:report => :get, :reports => :get, :facts => :get,
:environment_selected => :post, :architecture_selected => :post, :os_selected => :post},
:collection => { :show_search => :get}
map.dashboard '/dashboard', :controller => 'dashboard'
map.audit '/audit', :controller => 'audit'
map.statistics '/statistics', :controller => 'statistics'
test/functional/hosts_controller_test.rb
class HostsControllerTest < ActionController::TestCase
setup :initialize_host
test "should get index" do
def test_index
get :index
assert_response :success
assert_template 'index'
end
def test_show
get :show, :id => Host.first
assert_template 'show'
end
test "should get new" do
def test_new
get :new
assert_response :success
assert_template 'new'
end
test "should create new host" do
assert_difference 'Host.count' do
post :create, { :commit => "Create",
:record => {:name => "myotherfullhost",
:mac => "aabbecddee00",
:ip => "123.05.02.25",
:domain => {:id => Domain.find_or_create_by_name("othercompany.com").id.to_s},
:operatingsystem => {:id => Operatingsystem.first.id.to_s},
:architecture => {:id => Architecture.first.id.to_s},
:environment => {:id => Environment.first.id.to_s},
:disk => "empty partition"
}
}
end
end
test "should get edit" do
get :edit, :id => @host.id
assert_response :success
def test_create_invalid
Host.any_instance.stubs(:valid?).returns(false)
post :create
assert_template 'new'
end
test "should update host" do
put :update, { :commit => "Update", :id => @host.id, :record => {:disk => "ntfs"} }
@host = Host.find_by_id(@host.id)
def test_create_valid
Host.any_instance.stubs(:valid?).returns(true)
post :create, :host => {:name => "test"}
assert_redirected_to host_url(assigns(:host))
end
def test_edit
get :edit, :id => Host.first
assert_template 'edit'
end
assert @host.disk == "ntfs"
def test_update_invalid
Host.any_instance.stubs(:valid?).returns(false)
put :update, :id => Host.first
assert_template 'edit'
end
test "should destroy host" do
assert_difference('Host.count', -1) do
delete :destroy, :id => @host.id
end
def test_update_valid
Host.any_instance.stubs(:valid?).returns(true)
put :update, :id => Host.first
assert_redirected_to host_url(assigns(:host))
end
test "externalNodes should render 404 when no params are given" do
def test_destroy
host = Host.first
delete :destroy, :id => host
assert_redirected_to hosts_url
assert !Host.exists?(host.id)
end
test "externalNodes should render 404 when no params are given" do
get :externalNodes
assert_response :missing
assert_template :text => '404 Not Found'
test/unit/host_test.rb
test "should import facts from yaml of a new host" do
assert Host.importHostAndFacts(File.read(File.expand_path(File.dirname(__FILE__) + "/facts.yml")))
end
test "should not save if both ptable and disk are not defined" do
host = Host.create :name => "myfullhost", :mac => "aabbecddeeff", :ip => "123.05.02.03",
:domain => Domain.find_or_create_by_name("company.com"), :operatingsystem => Operatingsystem.first,
:architecture => Architecture.first, :environment => Environment.first
assert true unless SETTINGS[:attended]
assert !host.valid?
if SETTINGS[:unattended].nil? or SETTINGS[:unattended]
test "should not save if both ptable and disk are not defined" do
host = Host.create :name => "myfullhost", :mac => "aabbecddeeff", :ip => "123.05.02.03",
:domain => Domain.find_or_create_by_name("company.com"), :operatingsystem => Operatingsystem.first,
:architecture => Architecture.first, :environment => Environment.first
assert true unless SETTINGS[:attended]
assert !host.valid?
end
end
test "should save if ptable is defined" do

Also available in: Unified diff