Project

General

Profile

« Previous | Next » 

Revision e88536b2

Added by Daniel Lobato Garcia about 10 years ago

fixes #3214 - set taxonomy for hosts created via Puppet from facts or a default setting

View differences:

app/controllers/api/v2/hosts_controller.rb
param :type, String, :desc => "optional: the STI type of host to create"
def facts
@host, state = detect_host_type.importHostAndFacts params[:name], params[:facts], params[:certname], detected_proxy.try(:id)
@host, state = detect_host_type.import_host_and_facts params[:name], params[:facts], params[:certname], detected_proxy.try(:id)
process_response state
rescue ::Foreman::Exception => e
render :json => {'message'=>e.to_s}, :status => :unprocessable_entity
app/models/host/base.rb
super - [ inheritance_column ]
end
def self.importHostAndFacts json
def self.import_host_and_facts json
# noop, overridden by STI descendants
return self, true
end
# expect a facts hash
def importFacts facts
def import_facts facts
# we are not importing facts for hosts in build state (e.g. waiting for a re-installation)
raise ::Foreman::Exception.new('Host is pending for Build') if build?
......
FactImporter.importer_for(type).new(self, facts).import!
save(:validate => false)
populateFieldsFromFacts(facts)
populate_fields_from_facts(facts)
set_taxonomies(facts)
# we are saving here with no validations, as we want this process to be as fast
# as possible, assuming we already have all the right settings in Foreman.
......
attrs = [:model]
end
def populateFieldsFromFacts facts = self.facts_hash
def populate_fields_from_facts facts = self.facts_hash
# we don't import facts for host in build mode
return if build?
......
def normalize_name
self.name = Net::Validations.normalize_hostname(name) if self.name.present?
end
def set_taxonomies(facts)
['location', 'organization'].each do |taxonomy|
next unless SETTINGS["#{taxonomy.pluralize}_enabled".to_sym]
taxonomy_class = taxonomy.classify.constantize
if Setting["#{taxonomy}_fact"].present? && facts.keys.include?("#{taxonomy}_fact")
taxonomy_from_fact = taxonomy_class.find_by_title(facts["#{taxonomy}_fact"])
else
default_taxonomy = taxonomy_class.find_by_title(Setting["default_#{taxonomy}".to_sym])
end
if self.send("#{taxonomy}").present?
# Change taxonomy to fact taxonomy if set, otherwise leave it as is
self.send("#{taxonomy}=", taxonomy_from_fact) unless taxonomy_from_fact.nil?
else
# No taxonomy was set, set to fact taxonomy or default taxonomy
self.send "#{taxonomy}=", (taxonomy_from_fact || default_taxonomy)
end
end
end
end
end
app/models/host/managed.rb
end
# JSON is auto-parsed by the API, so these should be in the right format
def self.importHostAndFacts hostname, facts, certname = nil, proxy_id = nil
def self.import_host_and_facts hostname, facts, certname = nil, proxy_id = nil
raise(::Foreman::Exception.new("Invalid Facts, must be a Hash")) unless facts.is_a?(Hash)
raise(::Foreman::Exception.new("Invalid Hostname, must be a String")) unless hostname.is_a?(String)
......
hostname.try(:downcase!)
certname.try(:downcase!)
h = certname.present? ? Host.find_by_certname(certname) : nil
h ||= Host.find_by_name hostname
h ||= Host.new(:name => hostname, :certname => certname) if Setting[:create_new_host_when_facts_are_uploaded]
host = certname.present? ? Host.find_by_certname(certname) : nil
host ||= Host.find_by_name hostname
host ||= Host.new(:name => hostname, :certname => certname) if Setting[:create_new_host_when_facts_are_uploaded]
return Host.new, true if h.nil?
return Host.new, true if host.nil?
# if we were given a certname but found the Host by hostname we should update the certname
h.certname = certname if certname.present?
host.certname = certname if certname.present?
# if proxy authentication is enabled and we have no puppet proxy set, use it.
h.puppet_proxy_id ||= proxy_id
host.puppet_proxy_id ||= proxy_id
h.save(:validate => false) if h.new_record?
state = h.importFacts(facts)
return h, state
host.save(:validate => false) if host.new_record?
state = host.import_facts(facts)
return host, state
end
def attributes_to_import_from_facts
......
super + [:domain, :architecture, :operatingsystem] + attrs
end
def populateFieldsFromFacts facts = self.facts_hash
def populate_fields_from_facts facts = self.facts_hash
importer = super
normalize_addresses
if Setting[:update_environment_from_facts]
app/models/setting.rb
TYPES= %w{ integer boolean hash array string }
FROZEN_ATTRS = %w{ name category }
NONZERO_ATTRS = %w{ puppet_interval idle_timeout entries_per_page max_trend }
BLANK_ATTRS = %w{ trusted_puppetmaster_hosts login_delegation_logout_url authorize_login_delegation_auth_source_user_autocreate root_pass }
BLANK_ATTRS = %w{ trusted_puppetmaster_hosts login_delegation_logout_url authorize_login_delegation_auth_source_user_autocreate root_pass default_location default_organization }
URI_ATTRS = %w{ foreman_url unattended_url }
class UriValidator < ActiveModel::EachValidator
app/models/setting/puppet.rb
self.set('host_group_matchers_inheritance', N_("Foreman host group matchers will be inherited by children when evaluating smart class parameters"), true),
self.set('create_new_host_when_facts_are_uploaded', N_("Foreman will create the host when new facts are received"), true),
self.set('create_new_host_when_report_is_uploaded', N_("Foreman will create the host when a report is received"), true),
self.set('legacy_puppet_hostname', N_("Foreman will truncate hostname to 'puppet' if it starts with puppet"), false)
self.set('legacy_puppet_hostname', N_("Foreman will truncate hostname to 'puppet' if it starts with puppet"), false),
self.set('location_fact', N_("Hosts created after a puppet run will be placed in the location this fact dictates. The content of this fact should be the full label of the location."), 'foreman_location'),
self.set('organization_fact', N_("Hosts created after a puppet run will be placed in the organization this fact dictates. The content of this fact should be the full label of the organization."), 'foreman_organization'),
self.set('default_location', N_("Hosts created after a puppet run that did not send a location fact will be placed in this location"), ''),
self.set('default_organization', N_("Hosts created after a puppet run that did not send a organization fact will be placed in this organization"), '')
].compact.each { |s| self.create s.update(:category => "Setting::Puppet")}
true
lib/tasks/puppet.rake
next
end
if host.populateFieldsFromFacts
if host.populate_fields_from_facts
counter += 1
else
$stdout.puts "#{host.hostname}: #{host.errors.full_messages.join(", ")}"
......
puts "Importing #{name}"
puppet_facts = File.read(yaml)
facts_stripped_of_class_names = YAML::load(puppet_facts.gsub(/\!ruby\/object.*$/,''))
Host.importHostAndFacts facts_stripped_of_class_names['name'], facts_stripped_of_class_names['values'].with_indifferent_access
Host.import_host_and_facts facts_stripped_of_class_names['name'], facts_stripped_of_class_names['values'].with_indifferent_access
end
end
end
test/fixtures/settings.yml
category: Setting::Provisioning
default: http://foreman.some.host.fqdn
description: The URL Foreman should point to in templates etc
attributes44:
name: default_organization
category: Setting::Puppet
default: 'Organization 1'
description: 'Default organization when importing hosts'
attributes45:
name: default_location
category: Setting::Puppet
default: 'Location 1'
description: 'Default location when importing hosts'
attributes46:
name: location_fact
category: Setting::Puppet
default: 'foreman_location'
description: 'Fact to set location from when importing hosts'
attributes47:
name: organization_fact
category: Setting::Puppet
default: 'foreman_organization'
description: 'Fact to set organization from when importing hosts'
test/unit/host_test.rb
test "should import facts from json stream" do
h=Host.new(:name => "sinn1636.lan")
h.disk = "!" # workaround for now
assert h.importFacts(JSON.parse(File.read(File.expand_path(File.dirname(__FILE__) + "/facts.json")))['facts'])
assert h.import_facts(JSON.parse(File.read(File.expand_path(File.dirname(__FILE__) + "/facts.json")))['facts'])
end
test "should import facts from json of a new host when certname is not specified" do
refute Host.find_by_name('sinn1636.lan')
raw = parse_json_fixture('/facts.json')
assert Host.importHostAndFacts(raw['name'], raw['facts'])
assert Host.find_by_name('sinn1636.lan')
end
context 'import host and facts' do
test 'should import facts from json of a new host when certname is not specified' do
refute Host.find_by_name('sinn1636.lan')
raw = parse_json_fixture('/facts.json')
assert Host.import_host_and_facts(raw['name'], raw['facts'])
assert Host.find_by_name('sinn1636.lan')
end
test "should downcase hostname parameter from json of a new host" do
raw = parse_json_fixture('/facts_with_caps.json')
assert Host.importHostAndFacts(raw['name'], raw['facts'])
assert Host.find_by_name('sinn1636.lan')
end
test 'should downcase hostname parameter from json of a new host' do
raw = parse_json_fixture('/facts_with_caps.json')
assert Host.import_host_and_facts(raw['name'], raw['facts'])
assert Host.find_by_name('sinn1636.lan')
end
test "should import facts idempotently" do
raw = parse_json_fixture('/facts_with_caps.json')
assert Host.importHostAndFacts(raw['name'], raw['facts'])
value_ids = Host.find_by_name('sinn1636.lan').fact_values.map(&:id)
assert Host.importHostAndFacts(raw['name'], raw['facts'])
assert_equal value_ids.sort, Host.find_by_name('sinn1636.lan').fact_values.map(&:id).sort
end
test 'should import facts idempotently' do
raw = parse_json_fixture('/facts_with_caps.json')
assert Host.import_host_and_facts(raw['name'], raw['facts'])
value_ids = Host.find_by_name('sinn1636.lan').fact_values.map(&:id)
assert Host.import_host_and_facts(raw['name'], raw['facts'])
assert_equal value_ids.sort, Host.find_by_name('sinn1636.lan').fact_values.map(&:id).sort
end
test "should find a host by certname not fqdn when provided" do
Host.new(:name => 'sinn1636.fail', :certname => 'sinn1636.lan.cert').save(:validate => false)
assert Host.find_by_name('sinn1636.fail').ip.nil?
# hostname in the json is sinn1636.lan, so if the facts have been updated for
# this host, it's a successful identification by certname
raw = parse_json_fixture('/facts_with_certname.json')
assert Host.importHostAndFacts(raw['name'], raw['facts'], raw['certname'])
assert_equal '10.35.27.2', Host.find_by_name('sinn1636.fail').ip
end
test 'should find a host by certname not fqdn when provided' do
Host.new(:name => 'sinn1636.fail', :certname => 'sinn1636.lan.cert').save(:validate => false)
assert Host.find_by_name('sinn1636.fail').ip.nil?
# hostname in the json is sinn1636.lan, so if the facts have been updated for
# this host, it's a successful identification by certname
raw = parse_json_fixture('/facts_with_certname.json')
assert Host.import_host_and_facts(raw['name'], raw['facts'], raw['certname'])
assert_equal '10.35.27.2', Host.find_by_name('sinn1636.fail').ip
end
test "should update certname when host is found by hostname and certname is provided" do
Host.new(:name => 'sinn1636.lan', :certname => 'sinn1636.cert.fail').save(:validate => false)
assert_equal 'sinn1636.cert.fail', Host.find_by_name('sinn1636.lan').certname
raw = parse_json_fixture('/facts_with_certname.json')
assert Host.importHostAndFacts(raw['name'], raw['facts'], raw['certname'])
assert_equal 'sinn1636.lan.cert', Host.find_by_name('sinn1636.lan').certname
end
test 'should update certname when host is found by hostname and certname is provided' do
Host.new(:name => 'sinn1636.lan', :certname => 'sinn1636.cert.fail').save(:validate => false)
assert_equal 'sinn1636.cert.fail', Host.find_by_name('sinn1636.lan').certname
raw = parse_json_fixture('/facts_with_certname.json')
assert Host.import_host_and_facts(raw['name'], raw['facts'], raw['certname'])
assert_equal 'sinn1636.lan.cert', Host.find_by_name('sinn1636.lan').certname
end
test "host is created when uploading facts if setting is true" do
assert_difference 'Host.count' do
Setting[:create_new_host_when_facts_are_uploaded] = true
test 'host is created when uploading facts if setting is true' do
assert_difference 'Host.count' do
Setting[:create_new_host_when_facts_are_uploaded] = true
raw = parse_json_fixture('/facts_with_certname.json')
Host.import_host_and_facts(raw['name'], raw['facts'], raw['certname'])
assert Host.find_by_name('sinn1636.lan')
Setting[:create_new_host_when_facts_are_uploaded] =
Setting.find_by_name('create_new_host_when_facts_are_uploaded').default
end
end
test 'host is not created when uploading facts if setting is false' do
Setting[:create_new_host_when_facts_are_uploaded] = false
assert_equal false, Setting[:create_new_host_when_facts_are_uploaded]
raw = parse_json_fixture('/facts_with_certname.json')
Host.importHostAndFacts(raw['name'], raw['facts'], raw['certname'])
assert Host.find_by_name('sinn1636.lan')
assert Host.import_host_and_facts(raw['name'], raw['facts'], raw['certname'])
host = Host.find_by_name('sinn1636.lan')
Setting[:create_new_host_when_facts_are_uploaded] =
Setting.find_by_name("create_new_host_when_facts_are_uploaded").default
Setting.find_by_name('create_new_host_when_facts_are_uploaded').default
assert_nil host
end
end
test "host is not created when uploading facts if setting is false" do
Setting[:create_new_host_when_facts_are_uploaded] = false
assert_equal false, Setting[:create_new_host_when_facts_are_uploaded]
raw = parse_json_fixture('/facts_with_certname.json')
assert Host.importHostAndFacts(raw['name'], raw['facts'], raw['certname'])
host = Host.find_by_name('sinn1636.lan')
Setting[:create_new_host_when_facts_are_uploaded] =
Setting.find_by_name("create_new_host_when_facts_are_uploaded").default
assert_nil host
test 'host taxonomies are set to a default when uploading facts' do
Setting[:create_new_host_when_facts_are_uploaded] = true
raw = parse_json_fixture('/facts.json')
Host.import_host_and_facts(raw['name'], raw['facts'])
assert_equal Setting[:default_location], Host.find_by_name('sinn1636.lan').location.title
assert_equal Setting[:default_organization], Host.find_by_name('sinn1636.lan').organization.title
end
test 'host taxonomies are set to setting[taxonomy_fact] if it exists' do
Setting[:create_new_host_when_facts_are_uploaded] = true
raw = parse_json_fixture('/facts.json')
raw['facts']['location_fact'] = 'Location 2'
raw['facts']['organization_fact'] = 'Organization 2'
Host.import_host_and_facts(raw['name'], raw['facts'])
assert_equal 'Location 2', Host.find_by_name('sinn1636.lan').location.title
assert_equal 'Organization 2', Host.find_by_name('sinn1636.lan').organization.title
end
test 'default taxonomies are not assigned to hosts with taxonomies' do
Setting[:default_location] = taxonomies(:location1).title
raw = parse_json_fixture('/facts.json')
Host.import_host_and_facts(raw['name'], raw['facts'])
Host.find_by_name('sinn1636.lan').update_attribute(:location, taxonomies(:location2))
Host.find_by_name('sinn1636.lan').import_facts(raw['facts'])
assert_equal taxonomies(:location2), Host.find_by_name('sinn1636.lan').location
end
test 'taxonomies from facts override already existing taxonomies in hosts' do
Setting[:create_new_host_when_facts_are_uploaded] = true
raw = parse_json_fixture('/facts.json')
raw['facts']['location_fact'] = 'Location 2'
Host.import_host_and_facts(raw['name'], raw['facts'])
Host.find_by_name('sinn1636.lan').update_attribute(:location, taxonomies(:location1))
Host.find_by_name('sinn1636.lan').import_facts(raw['facts'])
assert_equal taxonomies(:location2), Host.find_by_name('sinn1636.lan').location
end
end
test "host is created when receiving a report if setting is true" do
......
end
assert_difference('Model.count') do
facts = JSON.parse(File.read(File.expand_path(File.dirname(__FILE__) + "/facts.json")))
h.populateFieldsFromFacts facts['facts']
h.populate_fields_from_facts facts['facts']
end
end
......
test "should update puppet_proxy_id to the id of the validated proxy" do
sp = smart_proxies(:puppetmaster)
raw = parse_json_fixture('/facts_with_caps.json')
Host.importHostAndFacts(raw['name'], raw['facts'], nil, sp.id)
Host.import_host_and_facts(raw['name'], raw['facts'], nil, sp.id)
assert_equal sp.id, Host.find_by_name('sinn1636.lan').puppet_proxy_id
end
......
Host.new(:name => 'sinn1636.lan', :puppet_proxy_id => smart_proxies(:puppetmaster).id).save(:validate => false)
sp = smart_proxies(:puppetmaster)
raw = parse_json_fixture('/facts_with_certname.json')
assert Host.importHostAndFacts(raw['name'], raw['facts'], nil, sp.id)
assert Host.import_host_and_facts(raw['name'], raw['facts'], nil, sp.id)
assert_equal smart_proxies(:puppetmaster).id, Host.find_by_name('sinn1636.lan').puppet_proxy_id
end

Also available in: Unified diff