· 7 years ago · Oct 15, 2018, 11:06 PM
1## fugitive:///home/olevy/git/foreman/.git//0/app/models/host.rb [ruby_on_rails]
2class Host < Puppet::Rails::Host
3 include Authorization
4 include ReportCommon
5 belongs_to :model
6 has_many :host_classes, :dependent => :destroy
7 has_many :puppetclasses, :through => :host_classes
8 belongs_to :hostgroup
9 has_many :reports, :dependent => :destroy
10 has_many :host_parameters, :dependent => :destroy, :foreign_key => :reference_id
11 accepts_nested_attributes_for :host_parameters, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
12 belongs_to :owner, :polymorphic => true
13
14 include Hostext::Search
15 include HostCommon
16
17 class Jail < Safemode::Jail
18 allow :name, :diskLayout, :puppetmaster, :operatingsystem, :os, :environment, :ptable, :hostgroup, :url_for_boot,
19 :params, :hostgroup, :domain, :ip, :mac
20 end
21
22 attr_accessor :cached_parameters
23
24 named_scope :recent, lambda { |*args| {:conditions => ["last_report > ?", (args.first || (Setting[:puppet_interval] + 5).minutes.ago)]} }
25 named_scope :out_of_sync, lambda { |*args| {:conditions => ["last_report < ? and enabled != ?", (args.first || (Setting[:puppet_interval] + 5).minutes.ago), false]} }
26
27 named_scope :with_fact, lambda { |fact,value|
28 unless fact.nil? or value.nil?
29 { :joins => "INNER JOIN fact_values fv_#{fact} ON fv_#{fact}.host_id = hosts.id
30 INNER JOIN fact_names fn_#{fact} ON fn_#{fact}.id = fv_#{fact}.fact_name_id",
31 :select => "DISTINCT hosts.name, hosts.id", :conditions =>
32 ["fv_#{fact}.value = ? and fn_#{fact}.name = ? and fv_#{fact}.fact_name_id = fn_#{fact}.id",value, fact ] }
33 else
34 raise "invalid fact"
35 end
36 }
37
38 named_scope :with_class, lambda { |klass|
39 unless klass.nil?
40 { :joins => :puppetclasses, :select => "hosts.name", :conditions => {:puppetclasses => {:name => klass }} }
41 else
42 raise "invalid class"
43 end
44 }
45
46 named_scope :with_error, { :conditions => "(puppet_status > 0) and
47 ((puppet_status >> #{BIT_NUM*METRIC.index("failed")} & #{MAX}) != 0) or
48 ((puppet_status >> #{BIT_NUM*METRIC.index("failed_restarts")} & #{MAX}) != 0)"
49 }
50
51 named_scope :with_changes, { :conditions => "(puppet_status > 0) and
52 ((puppet_status >> #{BIT_NUM*METRIC.index("applied")} & #{MAX}) != 0) or
53 ((puppet_status >> #{BIT_NUM*METRIC.index("restarted")} & #{MAX}) !=0)"
54 }
55
56 named_scope :successful, {:conditions => "puppet_status = 0"}
57 named_scope :alerts_disabled, {:conditions => ["enabled = ?", false] }
58
59 named_scope :my_hosts, lambda {
60 user = User.current
61 owner_conditions = sanitize_sql_for_conditions(["((hosts.owner_id in (?) AND hosts.owner_type = 'Usergroup') OR (hosts.owner_id = ? AND hosts.owner_type = 'User'))", user.my_usergroups.map(&:id), user.id])
62 domain_conditions = sanitize_sql_for_conditions([" (hosts.domain_id in (?))",dms = (user.domains).map(&:id)])
63 hostgroup_conditions = sanitize_sql_for_conditions([" (hosts.hostgroup_id in (?))",(hgs = user.hostgroups).map(&:id)])
64
65 fact_conditions = ""
66 for user_fact in (ufs = user.user_facts)
67 fact_conditions += sanitize_sql_for_conditions ["(hosts.id = fact_values.host_id and fact_values.fact_name_id = ? and fact_values.value #{user_fact.operator} ?)", user_fact.fact_name_id, user_fact.criteria]
68 fact_conditions = user_fact.andor == "and" ? "(#{fact_conditions}) and " : "#{fact_conditions} or "
69 end
70 if match = fact_conditions.match(/^(.*).....$/)
71 fact_conditions = "(#{match[1]})"
72 end
73
74 conditions = ""
75 if user.filtering?
76 conditions = "#{owner_conditions}" if user.filter_on_owner
77 (conditions = (user.domains_andor == "and") ? "(#{conditions}) and #{domain_conditions} " : "#{conditions} or #{domain_conditions} ") unless dms.empty?
78 (conditions = (user.hostgroups_andor == "and") ? "(#{conditions}) and #{hostgroup_conditions} " : "#{conditions} or #{hostgroup_conditions} ") unless hgs.empty?
79 (conditions = (user.facts_andor == "and") ? "(#{conditions}) and #{fact_conditions} " : "#{conditions} or #{fact_conditions} ") unless ufs.empty?
80 conditions.sub!(/\s*\(\)\s*/, "")
81 conditions.sub!(/^(?:\(\))?\s?(?:and|or)\s*/, "")
82 conditions.sub!(/\(\s*(?:or|and)\s*\(/, "((")
83 end
84 {:conditions => conditions}
85 }
86
87 named_scope :completer_scope, lambda { my_hosts.scope(:find) }
88
89 # audit the changes to this model
90 acts_as_audited :except => [:last_report, :puppet_status, :last_compile]
91
92 # some shortcuts
93 alias_attribute :os, :operatingsystem
94 alias_attribute :arch, :architecture
95 alias_attribute :hostname, :name
96 alias_attribute :fqdn, :name
97
98 validates_uniqueness_of :name
99 validates_presence_of :name, :environment_id
100 if SETTINGS[:unattended]
101 # handles all orchestration of smart proxies.
102 include Foreman::Renderer
103 include Orchestration
104 include HostTemplateHelpers
105
106 validates_uniqueness_of :ip, :if => Proc.new {|host| host.managed}
107 validates_uniqueness_of :mac, :unless => Proc.new { |host| host.hypervisor? or !host.managed }
108 validates_uniqueness_of :sp_mac, :allow_nil => true, :allow_blank => true
109 validates_uniqueness_of :sp_name, :sp_ip, :allow_blank => true, :allow_nil => true
110 validates_format_of :sp_name, :with => /.*-sp/, :allow_nil => true, :allow_blank => true
111 validates_presence_of :architecture_id, :operatingsystem_id, :if => Proc.new {|host| host.managed}
112 validates_presence_of :domain_id
113 validates_presence_of :mac, :unless => Proc.new { |host| host.hypervisor? or !host.managed }
114
115 validates_length_of :root_pass, :minimum => 8,:too_short => 'should be 8 characters or more'
116 validates_format_of :mac, :with => (/([a-f0-9]{1,2}:){5}[a-f0-9]{1,2}/), :unless => Proc.new { |host| host.hypervisor_id or !host.managed }
117 validates_format_of :ip, :with => (/(\d{1,3}\.){3}\d{1,3}/), :if => Proc.new {|host| host.managed}
118 validates_presence_of :ptable, :message => "cant be blank unless a custom partition has been defined",
119 :if => Proc.new { |host| host.managed and host.disk.empty? and not defined?(Rake) }
120 validates_format_of :sp_mac, :with => /([a-f0-9]{1,2}:){5}[a-f0-9]{1,2}/, :allow_nil => true, :allow_blank => true
121 validates_format_of :sp_ip, :with => /(\d{1,3}\.){3}\d{1,3}/, :allow_nil => true, :allow_blank => true
122 validates_format_of :serial, :with => /[01],\d{3,}n\d/, :message => "should follow this format: 0,9600n8", :allow_blank => true, :allow_nil => true
123 end
124
125 before_validation :set_hostgroup_defaults, :set_default_user, :normalize_addresses, :normalize_hostname
126 after_validation :ensure_assoications
127
128 def set_default_user
129 self.owner ||= User.current
130 end
131
132 def to_param
133 name
134 end
135
136 def <=>(other)
137 self.name <=> other.name
138 end
139
140 def shortname
141 domain.nil? ? name : name.chomp("." + domain.name)
142 end
143
144 # method to return the correct owner list for host edit owner select dropbox
145 def is_owned_by
146 owner.id_and_type if owner
147 end
148
149 # virtual attributes which sets the owner based on the user selection
150 # supports a simple user, or a usergroup
151 # selection parameter is expected to be an ActiveRecord id_and_type method (see Foreman's AR extentions).
152 def is_owned_by=(selection)
153 oid = User.find(selection.to_i) if selection =~ (/-Users$/)
154 oid = Usergroup.find(selection.to_i) if selection =~ (/-Usergroups$/)
155 self.owner = oid
156 end
157
158 def clearReports
159 # Remove any reports that may be held against this host
160 Report.delete_all("host_id = #{id}")
161 end
162
163 def clearFacts
164 FactValue.delete_all("host_id = #{id}")
165 end
166
167 # Called from the host build post install process to indicate that the base build has completed
168 # Build is cleared and the boot link and autosign entries are removed
169 # A site specific build script is called at this stage that can do site specific tasks
170 def built(installed = true)
171 self.build = false
172 self.installed_at = Time.now.utc if installed
173 # If this save fails then an exception is raised and further actions are not processed
174 unless Rails.env == "test"
175 # Disallow any auto signing for our host.
176 GW::Puppetca.disable name unless puppetca? or not Setting[:manage_puppetca]
177 GW::Tftp.remove mac unless respond_to?(:tftp?) and tftp?
178 end
179 self.save
180 rescue => e
181 logger.warn "Failed to set Build on #{self}: #{e}"
182 false
183 end
184
185 #retuns fqdn of host puppetmaster
186 def pm_fqdn
187 puppetmaster == "puppet" ? "puppet.#{domain.name}" : "#{puppetmaster}"
188 end
189
190 # Cleans Certificate and enable Autosign
191 # Called after a host is given their provisioning template
192 # Returns : Boolean status of the operation
193 def handle_ca
194 return true if Rails.env == "test"
195 return true unless Setting[:manage_puppetca]
196 if puppetca?
197 respond_to?(:initialize_puppetca) && initialize_puppetca && delCertificate && setAutosign
198 else
199 # Legacy CA handling
200 GW::Puppetca.clean(name) && GW::Puppetca.sign(name)
201 end
202 end
203
204 # returns the host correct disk layout, custom or common
205 def diskLayout
206 pxe_render((disk.empty? ? ptable.layout : disk).gsub("\r",""))
207 end
208
209 # returns a configuration template (such as kickstart) to a given host
210 def configTemplate opts = {}
211 opts[:kind] ||= "provision"
212 opts[:operatingsystem_id] ||= operatingsystem_id
213 opts[:hostgroup_id] ||= hostgroup_id
214 opts[:environment_id] ||= environment_id
215
216 ConfigTemplate.find_template opts
217 end
218
219 # reports methods
220
221 def error_count
222 %w[failed failed_restarts].sum {|f| status f}
223 end
224
225 def no_report
226 last_report.nil? or last_report < Time.now - (Setting[:puppet_interval] + 3).minutes and enabled?
227 end
228
229 def disabled?
230 not enabled?
231 end
232
233 # returns the list of puppetclasses a host is in.
234 def puppetclasses_names
235 return all_puppetclasses.collect {|c| c.name}
236 end
237
238 def all_puppetclasses
239 return hostgroup.nil? ? puppetclasses : (hostgroup.classes + puppetclasses).uniq
240 end
241
242 # provide information about each node, mainly used for puppet external nodes
243 # TODO: remove hard coded default parameters into some selectable values in the database.
244 def info
245 # Static parameters
246 param = {}
247 # maybe these should be moved to the common parameters, leaving them in for now
248 param["puppetmaster"] = puppetmaster.to_s
249 param["domainname"] = domain.fullname unless domain.nil? or domain.fullname.nil?
250 if Setting[:ignore_puppet_facts_for_provisioning]
251 param["ip"] = ip
252 param["mac"] = mac
253 end
254 param.update self.params
255
256 info_hash = {}
257 info_hash['classes'] = self.puppetclasses_names
258 info_hash['parameters'] = param
259 info_hash['environment'] = environment.to_s unless environment.nil? or environment.name.nil?
260
261 return info_hash
262 end
263
264 def params
265 return cached_parameters if cached_parameters
266
267 cached_parameters = {}
268 # read common parameters
269 CommonParameter.all.each {|p| cached_parameters.update Hash[p.name => p.value] }
270 # read domain parameters
271 domain.domain_parameters.each {|p| cached_parameters.update Hash[p.name => p.value] } unless domain.nil?
272 # read OS parameters
273 operatingsystem.os_parameters.each {|p| cached_parameters.update Hash[p.name => p.value] } unless operatingsystem.nil?
274 # read group parameters only if a host belongs to a group
275 cached_parameters.update hostgroup.parameters unless hostgroup.nil?
276 # and now read host parameters, override if required
277 host_parameters.each {|p| cached_parameters.update Hash[p.name => p.value] }
278
279 # lookup keys
280 if Setting["Enable_Smart_Variables_in_ENC"]
281 klasses = puppetclasses.map(&:id)
282 klasses += hostgroup.classes.map(&:id) if hostgroup
283 LookupKey.all(:conditions => {:puppetclass_id =>klasses.flatten } ).each do |k|
284 cached_parameters[k.to_s] = k.value_for(self)
285 end unless klasses.empty?
286 end
287
288 return cached_parameters
289 end
290
291 def self.importHostAndFacts yaml
292 facts = YAML::load yaml
293 return false unless facts.is_a?(Puppet::Node::Facts)
294
295 h=find_or_create_by_name(facts.name)
296 h.save(false) if h.new_record?
297 h.importFacts(facts)
298 end
299
300 # import host facts, required when running without storeconfigs.
301 # expect a Puppet::Node::Facts
302 def importFacts facts
303 raise "invalid Fact" unless facts.is_a?(Puppet::Node::Facts)
304
305 # we are not importing facts for hosts in build state (e.g. waiting for a re-installation)
306 raise "Host is pending for Build" if build
307 time = facts.values[:_timestamp]
308 time = time.to_time if time.is_a?(String)
309
310 # we are not doing anything we already processed this fact (or a newer one)
311 return true unless last_compile.nil? or (last_compile + 1.minute < time)
312
313 self.last_compile = time
314 # save all other facts - pre 0.25 it was called setfacts
315 respond_to?("merge_facts") ? self.merge_facts(facts.values) : self.setfacts(facts.values)
316 save(false)
317
318 # we want to import other information only if this host was never installed via Foreman
319 populateFieldsFromFacts if installed_at.nil?
320
321 # we are saving here with no validations, as we want this process to be as fast
322 # as possible, assuming we already have all the right settings in Foreman.
323 # If we don't (e.g. we never install the server via Foreman, we populate the fields from facts
324 # TODO: if it was installed by Foreman and there is a mismatch,
325 # we should probably send out an alert.
326 return self.save(false)
327
328 rescue Exception => e
329 logger.warn "Failed to save #{facts.name}: #{e}"
330 end
331
332 def fv name
333 v=fact_values.first(:select => "fact_values.value", :joins => :fact_name,
334 :conditions => "fact_names.name = '#{name}'")
335 v.value unless v.nil?
336 end
337
338 def populateFieldsFromFacts
339 unless Setting[:ignore_puppet_facts_for_provisioning]
340 self.mac = fv(:macaddress).downcase unless fv(:macaddress).blank?
341 self.ip = fv(:ipaddress) if ip.nil?
342 end
343 self.domain = Domain.find_or_create_by_name fv(:domain) unless fv(:domain).empty?
344 # On solaris architecture fact is harwareisa
345 if myarch=fv(:architecture) || fv(:hardwareisa)
346 self.arch=Architecture.find_or_create_by_name myarch unless myarch.empty?
347 end
348
349 # by default, puppet doesn't store an env name in the database
350 env = fv(:environment) || Setting[:default_puppet_environment]
351 if Setting[:update_environment_from_facts]
352 self.environment = Environment.find_or_create_by_name env
353 else
354 self.environment ||= Environment.find_or_create_by_name env
355 end
356
357 os_name = fv(:operatingsystem)
358 if orel = fv(:lsbdistrelease) || fv(:operatingsystemrelease)
359 major, minor = orel.split(".")
360 minor ||= ""
361 self.os = Operatingsystem.find_or_create_by_name_and_major_and_minor os_name, major, minor
362 end
363
364 unless self.model
365 modelname = fv(:productname) || fv(:model) || (fv(:is_virtual) == "true" ? fv(:virtual) : nil)
366 self.model = Model.find_or_create_by_name(modelname.strip) unless modelname.empty?
367 end
368
369 # again we are saving without validations as input is required (e.g. partition tables)
370 self.save(false)
371 end
372
373 # Called by build link in the list
374 # Build is set
375 # The boot link and autosign entry are created
376 # Any existing puppet certificates are deleted
377 # Any facts are discarded
378 def setBuild
379 clearFacts
380 clearReports
381
382 # ensures that the legacy TFTP code is not called when using a smart proxy.
383 unless respond_to?(:tftp?) and tftp?
384 return false unless GW::Tftp.create([mac, os.to_s.gsub(" ","-"), arch.name, serial])
385 end
386
387 self.build = true
388 self.save
389 errors.empty?
390 end
391
392 # this method accepts a puppets external node yaml output and generate a node in our setup
393 # it is assumed that you already have the node (e.g. imported by one of the rack tasks)
394 def importNode nodeinfo
395 myklasses= []
396 # puppet classes
397 nodeinfo["classes"].each do |klass|
398 if pc = Puppetclass.find_by_name(klass)
399 myklasses << pc
400 else
401 error = "Failed to import #{klass} for #{name}: doesn't exists in our database - ignoring"
402 logger.warn error
403 $stdout.puts error
404 end
405 self.puppetclasses = myklasses
406 end
407
408 # parameters are a bit more tricky, as some classifiers provide the facts as parameters as well
409 # not sure what is puppet priority about it, but we ignore it if has a fact with the same name.
410 # additionally, we don't import any non strings values, as puppet don't know what to do with those as well.
411
412 myparams = self.info["parameters"]
413 nodeinfo["parameters"].each_pair do |param,value|
414 next if fact_names.exists? :name => param
415 next unless value.is_a?(String)
416
417 # we already have this parameter
418 next if myparams.has_key?(param) and myparams[param] == value
419
420 unless (hp = self.host_parameters.create(:name => param, :value => value))
421 logger.warn "Failed to import #{param}/#{value} for #{name}: #{hp.errors.full_messages.join(", ")}"
422 $stdout.puts $!
423 end
424 end
425
426 self.save
427 end
428
429 # counts each association of a given host
430 # e.g. how many hosts belongs to each os
431 # returns sorted hash
432 def self.count_distribution assocication
433 output = {}
434 count(:group => assocication).each do |k,v|
435 begin
436 output[k.to_label] = v unless v == 0
437 rescue
438 logger.info "skipped #{k} as it has has no label"
439 end
440 end
441 output
442 end
443
444 # counts each association of a given host for HABTM relationships
445 # TODO: Merge these two into one method
446 # e.g. how many hosts belongs to each os
447 # returns sorted hash
448 def self.count_habtm assocication
449 output = {}
450 Host.count(:include => assocication.pluralize, :group => "#{assocication}_id").to_a.each do |a|
451 #Ugly Ugly Ugly - I guess I'm missing something basic here
452 label = eval(assocication.camelize).send("find",a[0].to_i).to_label if a[0]
453 output[label] = a[1]
454 end
455 output
456 end
457
458 def resources_chart(timerange = 1.day.ago)
459 data = {}
460 data[:applied], data[:failed], data[:restarted], data[:failed_restarts], data[:skipped] = [],[],[],[],[]
461 reports.recent(timerange).each do |r|
462 data[:applied] << "[ #{r.reported_at.to_i}000, #{r.applied} ]"
463 data[:failed] << "[ #{r.reported_at.to_i}000, #{r.failed} ]"
464 data[:restarted] << "[ #{r.reported_at.to_i}000, #{r.restarted} ]"
465 data[:failed_restarts] << "[ #{r.reported_at.to_i}000, #{r.failed_restarts} ]"
466 data[:skipped] << "[ #{r.reported_at.to_i}000, #{r.skipped} ]"
467 end
468 return data
469 end
470
471 def runtime_chart(timerange = 1.day.ago)
472 data = {}
473 data[:config], data[:runtime] = [], []
474 reports.recent(timerange).each do |r|
475 data[:config] << "[ #{r.reported_at.to_i}000, #{r.config_retrieval} ]"
476 data[:runtime] << "[ #{r.reported_at.to_i}000, #{r.runtime} ]"
477 end
478 return data
479 end
480
481 def classes_from_storeconfigs
482 klasses = resources.all(:conditions => {:restype => "Class"}, :select => :title, :order => :title)
483 klasses.map!(&:title).delete(:main)
484 return klasses
485 end
486
487 def can_be_build?
488 managed? and SETTINGS[:unattended] ? build == false : false
489 end
490
491 def facts_hash
492 hash = {}
493 fact_values.all(:include => :fact_name).collect do |fact|
494 hash[fact.fact_name.name] = fact.value
495 hash
496 end
497 return hash
498 end
499
500 def enforce_permissions operation
501 if operation == "edit" and new_record?
502 return true # We get called again with the operation being set to create
503 end
504 current = User.current
505 if (operation == "edit") or operation == "destroy"
506 if current.allowed_to?("#{operation}_hosts".to_sym)
507 return true if Host.my_hosts(current).include? self
508 end
509 else # create
510 if current.allowed_to?(:create_hosts)
511 # We are unconstrained
512 return true if current.domains.empty? and current.hostgroups.empty?
513 # We are constrained and the constraint is matched
514 return true if (!current.domains.empty? and current.domains.include?(domain)) or
515 (!current.hostgroups.empty? and current.hostgroups.include?(hostgroup))
516 end
517 end
518 errors.add_to_base "You do not have permission to #{operation} this host"
519 false
520 end
521
522 def sp_valid?
523 !sp_name.empty? and !sp_ip.empty? and !sp_mac.empty?
524 end
525
526 def jumpstart?
527 operatingsystem.family == "Solaris" and architecture.name =~/Sparc/i rescue false
528 end
529
530 def set_hostgroup_defaults
531 return unless hostgroup
532 assign_hostgroup_attributes(%w{environment domain puppetmaster_name puppetproxy})
533 assign_hostgroup_attributes(%w{operatingsystem medium architecture ptable root_pass subnet}) if SETTINGS[:unattended] and (new_record? or managed?)
534 assign_hostgroup_attributes(Vm::PROPERTIES) if new_record? and hostgroup.hypervisor?
535 end
536
537 private
538 # align common mac and ip address input
539 def normalize_addresses
540 # a helper for variable scoping
541 helper = []
542 [self.mac,self.sp_mac].each do |m|
543 unless m.empty?
544 m.downcase!
545 if m=~/[a-f0-9]{12}/
546 m = m.gsub(/(..)/){|mh| mh + ":"}[/.{17}/]
547 elsif mac=~/([a-f0-9]{1,2}:){5}[a-f0-9]{1,2}/
548 m = m.split(":").map{|nibble| "%02x" % ("0x" + nibble)}.join(":")
549 end
550 end
551 helper << m
552 end
553 self.mac, self.sp_mac = helper
554
555 helper = []
556 [self.ip,self.sp_ip].each do |i|
557 unless i.empty?
558 i = i.split(".").map{|nibble| nibble.to_i}.join(".") if i=~/(\d{1,3}\.){3}\d{1,3}/
559 end
560 helper << i
561 end
562 self.ip, self.sp_ip = helper
563 end
564
565 # ensure that host name is fqdn
566 # if the user inputted short name, the domain name will be appended
567 # this is done to ensure compatibility with puppet storeconfigs
568 def normalize_hostname
569 # no hostname was given or a domain was selected, since this is before validation we need to ignore
570 # it and let the validations to produce an error
571 return if name.empty?
572
573 if domain.nil? and name.match(/\./)
574 # try to assign the domain automatically based on our existing domains from the host FQDN
575 self.domain = Domain.all.select{|d| name.match(d.name)}.first rescue nil
576 else
577 # if our host is in short name, append the domain name
578 self.name += ".#{domain}" unless name =~ /.#{domain}$/i
579 end
580 end
581
582 def assign_hostgroup_attributes attrs = []
583 attrs.each do |attr|
584 eval("self.#{attr.to_s} ||= hostgroup.#{attr.to_s}")
585 end
586 end
587
588 # checks if the host association is a valid association for this host
589 def ensure_assoications
590 status = true
591 %w{ ptable medium architecture}.each do |e|
592 value = self.send(e.to_sym)
593 next if value.blank?
594 unless os.send(e.pluralize.to_sym).include?(value)
595 errors.add(e, "#{value} does not belong to #{os} operating system")
596 status = false
597 end
598 end if os
599
600 puppetclasses.uniq.each do |e|
601 unless environment.puppetclasses.include?(e)
602 errors.add(:puppetclasses, "#{e} does not belong to the #{environment} environment")
603 status = false
604 end
605 end if environment
606 status
607 end
608
609 # alias to ensure same method that resolves the last report between the hosts and reports tables.
610 def reported_at
611 last_report
612 end
613
614 # puppet report status table column name
615 def self.report_status
616 "puppet_status"
617 end
618end
619
620## ~/git/foreman/app/models/host.rb [ruby_on_rails]
621class Host < Puppet::Rails::Host
622 include Authorization
623 include ReportCommon
624 belongs_to :model
625 has_many :host_classes, :dependent => :destroy
626 has_many :puppetclasses, :through => :host_classes
627 belongs_to :hostgroup
628 has_many :reports, :dependent => :destroy
629 has_many :host_parameters, :dependent => :destroy, :foreign_key => :reference_id
630 accepts_nested_attributes_for :host_parameters, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
631 belongs_to :owner, :polymorphic => true
632
633 include Hostext::Search
634 include HostCommon
635
636 class Jail < Safemode::Jail
637 allow :name, :diskLayout, :puppetmaster, :operatingsystem, :os, :environment, :ptable, :hostgroup, :url_for_boot,
638 :params, :hostgroup, :domain, :ip, :mac
639 end
640
641 attr_accessor :cached_parameters
642
643 named_scope :recent, lambda { |*args| {:conditions => ["last_report > ?", (args.first || (Setting[:puppet_interval] + 5).minutes.ago)]} }
644 named_scope :out_of_sync, lambda { |*args| {:conditions => ["last_report < ? and enabled != ?", (args.first || (Setting[:puppet_interval] + 5).minutes.ago), false]} }
645
646 named_scope :with_fact, lambda { |fact,value|
647 unless fact.nil? or value.nil?
648 { :joins => "INNER JOIN fact_values fv_#{fact} ON fv_#{fact}.host_id = hosts.id
649 INNER JOIN fact_names fn_#{fact} ON fn_#{fact}.id = fv_#{fact}.fact_name_id",
650 :select => "DISTINCT hosts.name, hosts.id", :conditions =>
651 ["fv_#{fact}.value = ? and fn_#{fact}.name = ? and fv_#{fact}.fact_name_id = fn_#{fact}.id",value, fact ] }
652 else
653 raise "invalid fact"
654 end
655 }
656
657 named_scope :with_class, lambda { |klass|
658 unless klass.nil?
659 { :joins => :puppetclasses, :select => "hosts.name", :conditions => {:puppetclasses => {:name => klass }} }
660 else
661 raise "invalid class"
662 end
663 }
664
665 named_scope :with_error, { :conditions => "(puppet_status > 0) and
666 ((puppet_status >> #{BIT_NUM*METRIC.index("failed")} & #{MAX}) != 0) or
667 ((puppet_status >> #{BIT_NUM*METRIC.index("failed_restarts")} & #{MAX}) != 0)"
668 }
669
670 named_scope :with_changes, { :conditions => "(puppet_status > 0) and
671 ((puppet_status >> #{BIT_NUM*METRIC.index("applied")} & #{MAX}) != 0) or
672 ((puppet_status >> #{BIT_NUM*METRIC.index("restarted")} & #{MAX}) !=0)"
673 }
674
675 named_scope :successful, {:conditions => "puppet_status = 0"}
676 named_scope :alerts_disabled, {:conditions => ["enabled = ?", false] }
677
678 named_scope :my_hosts, lambda {
679 user = User.current
680 owner_conditions = sanitize_sql_for_conditions(["((hosts.owner_id in (?) AND hosts.owner_type = 'Usergroup') OR (hosts.owner_id = ? AND hosts.owner_type = 'User'))", user.my_usergroups.map(&:id), user.id])
681 domain_conditions = sanitize_sql_for_conditions([" (hosts.domain_id in (?))",dms = (user.domains).map(&:id)])
682 hostgroup_conditions = sanitize_sql_for_conditions([" (hosts.hostgroup_id in (?))",(hgs = user.hostgroups).map(&:id)])
683
684 fact_conditions = ""
685 for user_fact in (ufs = user.user_facts)
686 fact_conditions += sanitize_sql_for_conditions ["(hosts.id = fact_values.host_id and fact_values.fact_name_id = ? and fact_values.value #{user_fact.operator} ?)", user_fact.fact_name_id, user_fact.criteria]
687 fact_conditions = user_fact.andor == "and" ? "(#{fact_conditions}) and " : "#{fact_conditions} or "
688 end
689 if match = fact_conditions.match(/^(.*).....$/)
690 fact_conditions = "(#{match[1]})"
691 end
692
693 conditions = ""
694 if user.filtering?
695 conditions = "#{owner_conditions}" if user.filter_on_owner
696 (conditions = (user.domains_andor == "and") ? "(#{conditions}) and #{domain_conditions} " : "#{conditions} or #{domain_conditions} ") unless dms.empty?
697 (conditions = (user.hostgroups_andor == "and") ? "(#{conditions}) and #{hostgroup_conditions} " : "#{conditions} or #{hostgroup_conditions} ") unless hgs.empty?
698 (conditions = (user.facts_andor == "and") ? "(#{conditions}) and #{fact_conditions} " : "#{conditions} or #{fact_conditions} ") unless ufs.empty?
699 conditions.sub!(/\s*\(\)\s*/, "")
700 conditions.sub!(/^(?:\(\))?\s?(?:and|or)\s*/, "")
701 conditions.sub!(/\(\s*(?:or|and)\s*\(/, "((")
702 end
703 {:conditions => conditions}
704 }
705
706 named_scope :completer_scope, lambda { my_hosts.scope(:find) }
707
708 # audit the changes to this model
709 acts_as_audited :except => [:last_report, :puppet_status, :last_compile]
710
711 # some shortcuts
712 alias_attribute :os, :operatingsystem
713 alias_attribute :arch, :architecture
714 alias_attribute :hostname, :name
715 alias_attribute :fqdn, :name
716
717 validates_uniqueness_of :name
718 validates_presence_of :name, :environment_id
719 if SETTINGS[:unattended]
720 # handles all orchestration of smart proxies.
721 include Foreman::Renderer
722 include Orchestration
723 include HostTemplateHelpers
724
725 validates_uniqueness_of :ip, :if => Proc.new {|host| host.managed}
726 validates_uniqueness_of :mac, :unless => Proc.new { |host| host.hypervisor? or !host.managed }
727 validates_uniqueness_of :sp_mac, :allow_nil => true, :allow_blank => true
728 validates_uniqueness_of :sp_name, :sp_ip, :allow_blank => true, :allow_nil => true
729 validates_format_of :sp_name, :with => /.*-sp/, :allow_nil => true, :allow_blank => true
730 validates_presence_of :architecture_id, :operatingsystem_id, :if => Proc.new {|host| host.managed}
731 validates_presence_of :domain_id
732 validates_presence_of :mac, :unless => Proc.new { |host| host.hypervisor? or !host.managed }
733
734 validates_length_of :root_pass, :minimum => 8,:too_short => 'should be 8 characters or more'
735 validates_format_of :mac, :with => (/([a-f0-9]{1,2}:){5}[a-f0-9]{1,2}/), :unless => Proc.new { |host| host.hypervisor_id or !host.managed }
736 validates_format_of :ip, :with => (/(\d{1,3}\.){3}\d{1,3}/), :if => Proc.new {|host| host.managed}
737 validates_presence_of :ptable, :message => "cant be blank unless a custom partition has been defined",
738 :if => Proc.new { |host| host.managed and host.disk.empty? and not defined?(Rake) }
739 validates_format_of :sp_mac, :with => /([a-f0-9]{1,2}:){5}[a-f0-9]{1,2}/, :allow_nil => true, :allow_blank => true
740 validates_format_of :sp_ip, :with => /(\d{1,3}\.){3}\d{1,3}/, :allow_nil => true, :allow_blank => true
741 validates_format_of :serial, :with => /[01],\d{3,}n\d/, :message => "should follow this format: 0,9600n8", :allow_blank => true, :allow_nil => true
742 end
743
744 before_validation :set_hostgroup_defaults, :set_default_user, :normalize_addresses, :normalize_hostname
745 after_validation :ensure_assoications
746
747 def set_default_user
748 self.owner ||= User.current
749 end
750
751 def to_param
752 name
753 end
754
755 def <=>(other)
756 self.name <=> other.name
757 end
758
759 def shortname
760 domain.nil? ? name : name.chomp("." + domain.name)
761 end
762
763 # method to return the correct owner list for host edit owner select dropbox
764 def is_owned_by
765 owner.id_and_type if owner
766 end
767
768 # virtual attributes which sets the owner based on the user selection
769 # supports a simple user, or a usergroup
770 # selection parameter is expected to be an ActiveRecord id_and_type method (see Foreman's AR extentions).
771 def is_owned_by=(selection)
772 oid = User.find(selection.to_i) if selection =~ (/-Users$/)
773 oid = Usergroup.find(selection.to_i) if selection =~ (/-Usergroups$/)
774 self.owner = oid
775 end
776
777 def clearReports
778 # Remove any reports that may be held against this host
779 Report.delete_all("host_id = #{id}")
780 end
781
782 def clearFacts
783 FactValue.delete_all("host_id = #{id}")
784 end
785
786 # Called from the host build post install process to indicate that the base build has completed
787 # Build is cleared and the boot link and autosign entries are removed
788 # A site specific build script is called at this stage that can do site specific tasks
789 def built(installed = true)
790 self.build = false
791 self.installed_at = Time.now.utc if installed
792 # If this save fails then an exception is raised and further actions are not processed
793 unless Rails.env == "test"
794 # Disallow any auto signing for our host.
795 GW::Puppetca.disable name unless puppetca? or not Setting[:manage_puppetca]
796 GW::Tftp.remove mac unless respond_to?(:tftp?) and tftp?
797 end
798 self.save
799 rescue => e
800 logger.warn "Failed to set Build on #{self}: #{e}"
801 false
802 end
803
804 #retuns fqdn of host puppetmaster
805 def pm_fqdn
806 puppetmaster == "puppet" ? "puppet.#{domain.name}" : "#{puppetmaster}"
807 end
808
809 # Cleans Certificate and enable Autosign
810 # Called after a host is given their provisioning template
811 # Returns : Boolean status of the operation
812 def handle_ca
813 return true if Rails.env == "test"
814 return true unless Setting[:manage_puppetca]
815 if puppetca?
816 respond_to?(:initialize_puppetca) && initialize_puppetca && delCertificate && setAutosign
817 else
818 # Legacy CA handling
819 GW::Puppetca.clean(name) && GW::Puppetca.sign(name)
820 end
821 end
822
823 # returns the host correct disk layout, custom or common
824 def diskLayout
825 pxe_render((disk.empty? ? ptable.layout : disk).gsub("\r",""))
826 end
827
828 # returns a configuration template (such as kickstart) to a given host
829 def configTemplate opts = {}
830 opts[:kind] ||= "provision"
831 opts[:operatingsystem_id] ||= operatingsystem_id
832 opts[:hostgroup_id] ||= hostgroup_id
833 opts[:environment_id] ||= environment_id
834
835 ConfigTemplate.find_template opts
836 end
837
838 # reports methods
839
840 def error_count
841 %w[failed failed_restarts].sum {|f| status f}
842 end
843
844 def no_report
845 last_report.nil? or last_report < Time.now - (Setting[:puppet_interval] + 3).minutes and enabled?
846 end
847
848 def disabled?
849 not enabled?
850 end
851
852 # returns the list of puppetclasses a host is in.
853 def puppetclasses_names
854 return all_puppetclasses.collect {|c| c.name}
855 end
856
857 def all_puppetclasses
858 return hostgroup.nil? ? puppetclasses : (hostgroup.classes + puppetclasses).uniq
859 end
860
861 # provide information about each node, mainly used for puppet external nodes
862 # TODO: remove hard coded default parameters into some selectable values in the database.
863 def info
864 # Static parameters
865 param = {}
866 # maybe these should be moved to the common parameters, leaving them in for now
867 param["puppetmaster"] = puppetmaster.to_s
868 param["domainname"] = domain.fullname unless domain.nil? or domain.fullname.nil?
869 if Setting[:ignore_puppet_facts_for_provisioning]
870 param["ip"] = ip
871 param["mac"] = mac
872 end
873 param.update self.params
874
875 info_hash = {}
876 info_hash['classes'] = self.puppetclasses_names
877 info_hash['parameters'] = param
878 info_hash['environment'] = environment.to_s unless environment.nil? or environment.name.nil?
879
880 return info_hash
881 end
882
883 def params
884 return cached_parameters if cached_parameters
885
886 cached_parameters = {}
887 # read common parameters
888 CommonParameter.all.each {|p| cached_parameters.update Hash[p.name => p.value] }
889 # read domain parameters
890 domain.domain_parameters.each {|p| cached_parameters.update Hash[p.name => p.value] } unless domain.nil?
891 # read OS parameters
892 operatingsystem.os_parameters.each {|p| cached_parameters.update Hash[p.name => p.value] } unless operatingsystem.nil?
893 # read group parameters only if a host belongs to a group
894 cached_parameters.update hostgroup.parameters unless hostgroup.nil?
895 # and now read host parameters, override if required
896 host_parameters.each {|p| cached_parameters.update Hash[p.name => p.value] }
897
898 # lookup keys
899 if Setting["Enable_Smart_Variables_in_ENC"]
900 klasses = puppetclasses.map(&:id)
901 klasses += hostgroup.classes.map(&:id) if hostgroup
902 LookupKey.all(:conditions => {:puppetclass_id =>klasses.flatten } ).each do |k|
903 cached_parameters[k.to_s] = k.value_for(self)
904 end unless klasses.empty?
905 end
906
907 return cached_parameters
908 end
909
910 def self.importHostAndFacts yaml
911 facts = YAML::load yaml
912 return false unless facts.is_a?(Puppet::Node::Facts)
913
914 h=find_or_create_by_name(facts.name)
915 h.save(false) if h.new_record?
916 h.importFacts(facts)
917 end
918
919 # import host facts, required when running without storeconfigs.
920 # expect a Puppet::Node::Facts
921 def importFacts facts
922 raise "invalid Fact" unless facts.is_a?(Puppet::Node::Facts)
923
924 # we are not importing facts for hosts in build state (e.g. waiting for a re-installation)
925 raise "Host is pending for Build" if build
926 time = facts.values[:_timestamp]
927 time = time.to_time if time.is_a?(String)
928
929 # we are not doing anything we already processed this fact (or a newer one)
930 return true unless last_compile.nil? or (last_compile + 1.minute < time)
931
932 self.last_compile = time
933 # save all other facts - pre 0.25 it was called setfacts
934 respond_to?("merge_facts") ? self.merge_facts(facts.values) : self.setfacts(facts.values)
935 save(false)
936
937 # we want to import other information only if this host was never installed via Foreman
938 populateFieldsFromFacts if installed_at.nil?
939
940 # we are saving here with no validations, as we want this process to be as fast
941 # as possible, assuming we already have all the right settings in Foreman.
942 # If we don't (e.g. we never install the server via Foreman, we populate the fields from facts
943 # TODO: if it was installed by Foreman and there is a mismatch,
944 # we should probably send out an alert.
945 return self.save(false)
946
947 rescue Exception => e
948 logger.warn "Failed to save #{facts.name}: #{e}"
949 end
950
951 def fv name
952 v=fact_values.first(:select => "fact_values.value", :joins => :fact_name,
953 :conditions => "fact_names.name = '#{name}'")
954 v.value unless v.nil?
955 end
956
957 def populateFieldsFromFacts
958 unless Setting[:ignore_puppet_facts_for_provisioning]
959 self.mac = fv(:macaddress).downcase unless fv(:macaddress).blank?
960 self.ip = fv(:ipaddress) if ip.nil?
961 end
962 self.domain = Domain.find_or_create_by_name fv(:domain) unless fv(:domain).empty?
963 # On solaris architecture fact is harwareisa
964 if myarch=fv(:architecture) || fv(:hardwareisa)
965 self.arch=Architecture.find_or_create_by_name myarch unless myarch.empty?
966 end
967
968 # by default, puppet doesn't store an env name in the database
969 env = fv(:environment) || Setting[:default_puppet_environment]
970 if Setting[:update_environment_from_facts]
971 self.environment = Environment.find_or_create_by_name env
972 else
973 self.environment ||= Environment.find_or_create_by_name env
974 end
975
976 os_name = fv(:operatingsystem)
977 if orel = fv(:lsbdistrelease) || fv(:operatingsystemrelease)
978 major, minor = orel.split(".")
979 minor ||= ""
980 self.os = Operatingsystem.find_or_create_by_name_and_major_and_minor os_name, major, minor
981 end
982
983 unless self.model
984 modelname = fv(:productname) || fv(:model) || (fv(:is_virtual) == "true" ? fv(:virtual) : nil)
985 self.model = Model.find_or_create_by_name(modelname.strip) unless modelname.empty?
986 end
987
988 # again we are saving without validations as input is required (e.g. partition tables)
989 self.save(false)
990 end
991
992 # Called by build link in the list
993 # Build is set
994 # The boot link and autosign entry are created
995 # Any existing puppet certificates are deleted
996 # Any facts are discarded
997 def setBuild
998 clearFacts
999 clearReports
1000
1001 # ensures that the legacy TFTP code is not called when using a smart proxy.
1002 unless respond_to?(:tftp?) and tftp?
1003 return false unless GW::Tftp.create([mac, os.to_s.gsub(" ","-"), arch.name, serial])
1004 end
1005
1006 self.build = true
1007 self.save
1008 errors.empty?
1009 end
1010
1011 # this method accepts a puppets external node yaml output and generate a node in our setup
1012 # it is assumed that you already have the node (e.g. imported by one of the rack tasks)
1013 def importNode nodeinfo
1014 myklasses= []
1015 # puppet classes
1016 nodeinfo["classes"].each do |klass|
1017 if pc = Puppetclass.find_by_name(klass)
1018 myklasses << pc
1019 else
1020 error = "Failed to import #{klass} for #{name}: doesn't exists in our database - ignoring"
1021 logger.warn error
1022 $stdout.puts error
1023 end
1024 self.puppetclasses = myklasses
1025 end
1026
1027 # parameters are a bit more tricky, as some classifiers provide the facts as parameters as well
1028 # not sure what is puppet priority about it, but we ignore it if has a fact with the same name.
1029 # additionally, we don't import any non strings values, as puppet don't know what to do with those as well.
1030
1031 myparams = self.info["parameters"]
1032 nodeinfo["parameters"].each_pair do |param,value|
1033 next if fact_names.exists? :name => param
1034 next unless value.is_a?(String)
1035
1036 # we already have this parameter
1037 next if myparams.has_key?(param) and myparams[param] == value
1038
1039 unless (hp = self.host_parameters.create(:name => param, :value => value))
1040 logger.warn "Failed to import #{param}/#{value} for #{name}: #{hp.errors.full_messages.join(", ")}"
1041 $stdout.puts $!
1042 end
1043 end
1044
1045 self.save
1046 end
1047
1048 # counts each association of a given host
1049 # e.g. how many hosts belongs to each os
1050 # returns sorted hash
1051 def self.count_distribution assocication
1052 output = {}
1053 count(:group => assocication).each do |k,v|
1054 begin
1055 output[k.to_label] = v unless v == 0
1056 rescue
1057 logger.info "skipped #{k} as it has has no label"
1058 end
1059 end
1060 output
1061 end
1062
1063 # counts each association of a given host for HABTM relationships
1064 # TODO: Merge these two into one method
1065 # e.g. how many hosts belongs to each os
1066 # returns sorted hash
1067 def self.count_habtm assocication
1068 output = {}
1069 Host.count(:include => assocication.pluralize, :group => "#{assocication}_id").to_a.each do |a|
1070 #Ugly Ugly Ugly - I guess I'm missing something basic here
1071 label = eval(assocication.camelize).send("find",a[0].to_i).to_label if a[0]
1072 output[label] = a[1]
1073 end
1074 output
1075 end
1076
1077 def resources_chart(timerange = 1.day.ago)
1078 data = {}
1079 data[:applied], data[:failed], data[:restarted], data[:failed_restarts], data[:skipped] = [],[],[],[],[]
1080 reports.recent(timerange).each do |r|
1081 data[:applied] << "[ #{r.reported_at.to_i}000, #{r.applied} ]"
1082 data[:failed] << "[ #{r.reported_at.to_i}000, #{r.failed} ]"
1083 data[:restarted] << "[ #{r.reported_at.to_i}000, #{r.restarted} ]"
1084 data[:failed_restarts] << "[ #{r.reported_at.to_i}000, #{r.failed_restarts} ]"
1085 data[:skipped] << "[ #{r.reported_at.to_i}000, #{r.skipped} ]"
1086 end
1087 return data
1088 end
1089
1090 def runtime_chart(timerange = 1.day.ago)
1091 data = {}
1092 data[:config], data[:runtime] = [], []
1093 reports.recent(timerange).each do |r|
1094 data[:config] << "[ #{r.reported_at.to_i}000, #{r.config_retrieval} ]"
1095 data[:runtime] << "[ #{r.reported_at.to_i}000, #{r.runtime} ]"
1096 end
1097 return data
1098 end
1099
1100 def classes_from_storeconfigs
1101 klasses = resources.all(:conditions => {:restype => "Class"}, :select => :title, :order => :title)
1102 klasses.map!(&:title).delete(:main)
1103 return klasses
1104 end
1105
1106 def can_be_build?
1107 managed? and SETTINGS[:unattended] ? build == false : false
1108 end
1109
1110 def facts_hash
1111 hash = {}
1112 fact_values.all(:include => :fact_name).collect do |fact|
1113 hash[fact.fact_name.name] = fact.value
1114 hash
1115 end
1116 return hash
1117 end
1118
1119 def enforce_permissions operation
1120 if operation == "edit" and new_record?
1121 return true # We get called again with the operation being set to create
1122 end
1123 current = User.current
1124 if (operation == "edit") or operation == "destroy"
1125 if current.allowed_to?("#{operation}_hosts".to_sym)
1126 return true if Host.my_hosts(current).include? self
1127 end
1128 else # create
1129 if current.allowed_to?(:create_hosts)
1130 # We are unconstrained
1131 return true if current.domains.empty? and current.hostgroups.empty?
1132 # We are constrained and the constraint is matched
1133 return true if (!current.domains.empty? and current.domains.include?(domain)) or
1134 (!current.hostgroups.empty? and current.hostgroups.include?(hostgroup))
1135 end
1136 end
1137 errors.add_to_base "You do not have permission to #{operation} this host"
1138 false
1139 end
1140
1141 def sp_valid?
1142 !sp_name.empty? and !sp_ip.empty? and !sp_mac.empty?
1143 end
1144
1145 def jumpstart?
1146 operatingsystem.family == "Solaris" and architecture.name =~/Sparc/i rescue false
1147 end
1148
1149 def set_hostgroup_defaults
1150 return unless hostgroup
1151 assign_hostgroup_attributes(%w{environment domain puppetmaster_name puppetproxy})
1152 assign_hostgroup_attributes(%w{operatingsystem medium architecture ptable root_pass subnet}) if SETTINGS[:unattended] and (new_record? or managed?)
1153 assign_hostgroup_attributes(Vm::PROPERTIES) if new_record? and hostgroup.hypervisor?
1154 end
1155
1156 private
1157 # align common mac and ip address input
1158 def normalize_addresses
1159 # a helper for variable scoping
1160 helper = []
1161 [self.mac,self.sp_mac].each do |m|
1162 unless m.empty?
1163 m.downcase!
1164 if m=~/[a-f0-9]{12}/
1165 m = m.gsub(/(..)/){|mh| mh + ":"}[/.{17}/]
1166 elsif mac=~/([a-f0-9]{1,2}:){5}[a-f0-9]{1,2}/
1167 m = m.split(":").map{|nibble| "%02x" % ("0x" + nibble)}.join(":")
1168 end
1169 end
1170 helper << m
1171 end
1172 self.mac, self.sp_mac = helper
1173
1174 helper = []
1175 [self.ip,self.sp_ip].each do |i|
1176 unless i.empty?
1177 i = i.split(".").map{|nibble| nibble.to_i}.join(".") if i=~/(\d{1,3}\.){3}\d{1,3}/
1178 end
1179 helper << i
1180 end
1181 self.ip, self.sp_ip = helper
1182 end
1183
1184 # ensure that host name is fqdn
1185 # if the user inputted short name, the domain name will be appended
1186 # this is done to ensure compatibility with puppet storeconfigs
1187 def normalize_hostname
1188 # no hostname was given or a domain was selected, since this is before validation we need to ignore
1189 # it and let the validations to produce an error
1190 return if name.empty?
1191
1192 if domain.nil? and name.match(/\./)
1193 # try to assign the domain automatically based on our existing domains from the host FQDN
1194 self.domain = Domain.all.select{|d| name.match(d.name)}.first rescue nil
1195 else
1196 # if our host is in short name, append the domain name
1197 self.name += ".#{domain}" unless name =~ /.#{domain}$/i
1198 end
1199 end
1200
1201 def assign_hostgroup_attributes attrs = []
1202 attrs.each do |attr|
1203 eval("self.#{attr.to_s} ||= hostgroup.#{attr.to_s}")
1204 end
1205 end
1206
1207 # checks if the host association is a valid association for this host
1208 def ensure_assoications
1209 status = true
1210 %w{ ptable medium architecture}.each do |e|
1211 value = self.send(e.to_sym)
1212 next if value.blank?
1213 unless os.send(e.pluralize.to_sym).include?(value)
1214 errors.add(e, "#{value} does not belong to #{os} operating system")
1215 status = false
1216 end
1217 end if os
1218
1219 puppetclasses.uniq.each do |e|
1220 unless environment.puppetclasses.include?(e)
1221 errors.add(:puppetclasses, "#{e} does not belong to the #{environment} environment")
1222 status = false
1223 end
1224 end if environment
1225 status
1226 end
1227
1228 # alias to ensure same method that resolves the last report between the hosts and reports tables.
1229 def reported_at
1230 last_report
1231 end
1232
1233 # puppet report status table column name
1234 def self.report_status
1235 "puppet_status"
1236 end
1237
1238 def as_json(options={})
1239 super({:methods => [:info], :include => [:environment]}.merge(options))
1240 end
1241end