Revision e88536b2
Added by Daniel Lobato Garcia about 10 years ago
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
fixes #3214 - set taxonomy for hosts created via Puppet from facts or a default setting