Revision 715d097c
Added by Shimon Shtein over 6 years ago
app/controllers/api/v2/hosts_controller.rb | ||
---|---|---|
param :type, String, :desc => N_("optional: the STI type of host to create")
|
||
|
||
def facts
|
||
@host = detect_host_type.import_host params[:name], params[:facts][:_type] || 'puppet', params[:certname], detected_proxy.try(:id)
|
||
state = @host.import_facts(params[:facts].to_unsafe_h)
|
||
@host = detect_host_type.import_host params[:name], params[:certname]
|
||
state = @host.import_facts(params[:facts].to_unsafe_h, detected_proxy)
|
||
process_response state
|
||
rescue ::Foreman::Exception => e
|
||
render_message(e.to_s, :status => :unprocessable_entity)
|
app/models/concerns/facets/base.rb | ||
---|---|---|
|
||
# Use this method to populate host's fields based on fact values exposed by the importer.
|
||
# You can populate fields in the associated host's facets too.
|
||
def populate_fields_from_facts(host, importer, type, proxy_id)
|
||
def populate_fields_from_facts(host, parser, type, proxy)
|
||
end
|
||
end
|
||
end
|
app/models/concerns/facets/managed_host_extensions.rb | ||
---|---|---|
attributes
|
||
end
|
||
|
||
def populate_facet_fields(parser, type, source_proxy)
|
||
Facets.registered_facets.values.each do |facet_config|
|
||
facet_config.model.populate_fields_from_facts(self, parser, type, source_proxy)
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
def forward_property_call(property, args, facet)
|
app/models/concerns/puppet_host_extensions.rb | ||
---|---|---|
module PuppetHostExtensions
|
||
def populate_fields_from_facts(parser, type, source_proxy)
|
||
super
|
||
|
||
type ||= 'puppet'
|
||
return unless type == 'puppet'
|
||
|
||
if Setting[:update_environment_from_facts]
|
||
set_non_empty_values parser, [:environment]
|
||
else
|
||
self.environment ||= parser.environment unless parser.environment.blank?
|
||
end
|
||
|
||
# if proxy authentication is enabled and we have no puppet proxy set and the upload came from puppet,
|
||
# use it as puppet proxy.
|
||
self.puppet_proxy ||= source_proxy
|
||
end
|
||
end
|
app/models/host/base.rb | ||
---|---|---|
super - [ inheritance_column ]
|
||
end
|
||
|
||
def self.import_host(hostname, certname = nil, deprecated_proxy = nil)
|
||
raise(::Foreman::Exception.new("Invalid Hostname, must be a String")) unless hostname.is_a?(String)
|
||
Foreman::Deprecation.deprecation_warning("1.19", "proxy parameter is deprecated, please use import_facts to set it") if deprecated_proxy
|
||
|
||
# downcase everything
|
||
hostname.try(:downcase!)
|
||
certname.try(:downcase!)
|
||
|
||
host = Host.find_by_certname(certname) if certname.present?
|
||
host ||= Host.find_by_name(hostname)
|
||
host ||= self.new(:name => hostname) # if no host was found, build a new one
|
||
|
||
# if we were given a certname but found the Host by hostname we should update the certname
|
||
# this also sets certname for newly created hosts
|
||
host.certname = certname if certname.present?
|
||
|
||
host
|
||
end
|
||
|
||
def create_new_host_when_facts_are_uploaded?
|
||
Setting[:create_new_host_when_facts_are_uploaded]
|
||
end
|
||
|
||
# expect a facts hash
|
||
def import_facts(facts)
|
||
def import_facts(facts, source_proxy = nil)
|
||
return false if !create_new_host_when_facts_are_uploaded? && new_record?
|
||
|
||
# we are not importing facts for hosts in build state (e.g. waiting for a re-installation)
|
||
... | ... | |
|
||
facts[:domain] = facts[:domain].downcase if facts[:domain].present?
|
||
|
||
type = facts.delete(:_type)
|
||
importer = FactImporter.importer_for(type).new(self, facts)
|
||
importer.import!
|
||
|
||
save(:validate => false)
|
||
|
||
parse_facts facts, type, source_proxy
|
||
end
|
||
|
||
def parse_facts(facts, type, source_proxy)
|
||
time = facts[:_timestamp]
|
||
time = time.to_time if time.is_a?(String)
|
||
self.last_compile = time if time
|
||
|
||
type = facts.delete(:_type) || 'puppet'
|
||
importer = FactImporter.importer_for(type).new(self, facts)
|
||
importer.import!
|
||
unless build?
|
||
parser = FactParser.parser_for(type).new(facts)
|
||
|
||
populate_fields_from_facts(parser, type, source_proxy)
|
||
end
|
||
|
||
save(:validate => false)
|
||
set_taxonomies(facts)
|
||
populate_fields_from_facts(facts, type)
|
||
|
||
# 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.
|
||
... | ... | |
[ :model ]
|
||
end
|
||
|
||
def populate_fields_from_facts(facts = self.facts_hash, type = 'puppet')
|
||
# we don't import facts for host in build mode
|
||
return if build?
|
||
|
||
parser = FactParser.parser_for(type).new(facts)
|
||
|
||
def populate_fields_from_facts(parser, type, source_proxy)
|
||
# we must create interface if it's missing so we can store domain
|
||
build_required_interfaces(:managed => false)
|
||
set_non_empty_values(parser, attributes_to_import_from_facts)
|
||
set_interfaces(parser) if parser.parse_interfaces?
|
||
parser
|
||
end
|
||
|
||
def set_non_empty_values(parser, methods)
|
app/models/host/managed.rb | ||
---|---|---|
setAutosign
|
||
end
|
||
|
||
def import_facts(facts)
|
||
def import_facts(facts, source_proxy = nil)
|
||
# Facts come from 'existing' attributes/infrastructure. We skip triggering
|
||
# the orchestration of this infrastructure when we create a host this way.
|
||
skip_orchestration! if SETTINGS[:unattended]
|
||
super(facts)
|
||
super
|
||
ensure
|
||
enable_orchestration! if SETTINGS[:unattended]
|
||
end
|
||
... | ... | |
all_puppetclasses.collect {|c| c.name}
|
||
end
|
||
|
||
def self.import_host(hostname, import_type, certname = nil, proxy_id = nil)
|
||
raise(::Foreman::Exception.new("Invalid Hostname, must be a String")) unless hostname.is_a?(String)
|
||
|
||
# downcase everything
|
||
hostname.try(:downcase!)
|
||
certname.try(:downcase!)
|
||
|
||
host = Host.find_by_certname(certname) if certname.present?
|
||
host ||= Host.find_by_name(hostname)
|
||
host ||= Host.new(:name => hostname) # if no host was found, build a new one
|
||
|
||
# if we were given a certname but found the Host by hostname we should update the certname
|
||
# this also sets certname for newly created hosts
|
||
host.certname = certname if certname.present?
|
||
|
||
# if proxy authentication is enabled and we have no puppet proxy set and the upload came from puppet,
|
||
# use it as puppet proxy.
|
||
host.puppet_proxy_id ||= proxy_id if import_type == 'puppet'
|
||
|
||
host
|
||
end
|
||
|
||
def attributes_to_import_from_facts
|
||
attrs = [:architecture, :hostgroup]
|
||
if !Setting[:ignore_facts_for_operatingsystem] || (Setting[:ignore_facts_for_operatingsystem] && operatingsystem.blank?)
|
||
... | ... | |
super + attrs
|
||
end
|
||
|
||
def populate_fields_from_facts(facts = self.facts_hash, type = 'puppet')
|
||
importer = super
|
||
if Setting[:update_environment_from_facts]
|
||
set_non_empty_values importer, [:environment]
|
||
else
|
||
self.environment ||= importer.environment unless importer.environment.blank?
|
||
end
|
||
def populate_fields_from_facts(parser, type, source_proxy)
|
||
super
|
||
operatingsystem.architectures << architecture if operatingsystem && architecture && !operatingsystem.architectures.include?(architecture)
|
||
self.save(:validate => false)
|
||
|
||
populate_facet_fields(parser, type, source_proxy)
|
||
end
|
||
|
||
# Called by build link in the list
|
app/services/fact_importer.rb | ||
---|---|---|
attr_reader :counters
|
||
|
||
def self.importer_for(type)
|
||
importers[type.to_s] || importers[:puppet]
|
||
importers[type.to_s]
|
||
end
|
||
|
||
def self.importers
|
||
@importers ||= { :puppet => PuppetFactImporter }.with_indifferent_access
|
||
@importers ||= {}.with_indifferent_access
|
||
end
|
||
|
||
def self.register_fact_importer(key, klass)
|
||
def self.register_fact_importer(key, klass, default = false)
|
||
importers.default = klass if default
|
||
|
||
importers[key.to_sym] = klass
|
||
end
|
||
|
app/services/fact_parser.rb | ||
---|---|---|
VIRTUAL_NAMES = /#{VIRTUAL}|#{BRIDGES}|#{BONDS}/
|
||
|
||
def self.parser_for(type)
|
||
parsers[type.to_s] || parsers[:puppet]
|
||
parsers[type.to_s]
|
||
end
|
||
|
||
def self.parsers
|
||
@parsers ||= { :puppet => PuppetFactParser }.with_indifferent_access
|
||
@parsers ||= {}.with_indifferent_access
|
||
end
|
||
|
||
def self.register_fact_parser(key, klass)
|
||
def self.register_fact_parser(key, klass, default = false)
|
||
parsers.default = klass if default
|
||
parsers[key.to_sym] = klass
|
||
end
|
||
|
config/initializers/puppet.rb | ||
---|---|---|
:partial => "hosts/puppet/main_tab_fields",
|
||
:priority => 100
|
||
end
|
||
|
||
FactImporter.register_fact_importer :puppet, PuppetFactImporter, true
|
||
FactParser.register_fact_parser :puppet, PuppetFactParser, true
|
||
|
||
# The module should be included after the class is constructed,
|
||
# since it tries to alias_method_chain a method that is defined
|
||
# in the class itself.
|
||
Host::Managed.send :prepend, PuppetHostExtensions
|
test/models/host_test.rb | ||
---|---|---|
test "should populate primary interface attributes even without existing interface" do
|
||
host = FactoryGirl.create(:host, :managed => false)
|
||
host.interfaces = []
|
||
host.populate_fields_from_facts(:domain => 'example.com',
|
||
:operatingsystem => 'RedHat',
|
||
:operatingsystemrelease => '6.2',
|
||
:macaddress_eth0 => '00:00:11:22:11:22',
|
||
:ipaddress_eth0 => '192.168.0.1',
|
||
:ipaddress6_eth0 => '2001:db8::1',
|
||
:interfaces => 'eth0')
|
||
host.populate_fields_from_facts(
|
||
mock_parser(:domain => 'example.com',
|
||
:operatingsystem => 'RedHat',
|
||
:operatingsystemrelease => '6.2',
|
||
:macaddress_eth0 => '00:00:11:22:11:22',
|
||
:ipaddress_eth0 => '192.168.0.1',
|
||
:ipaddress6_eth0 => '2001:db8::1',
|
||
:interfaces => 'eth0'),
|
||
'puppet',
|
||
nil)
|
||
assert_equal 'example.com', host.domain.name
|
||
assert_equal '2001:db8::1', host.primary_interface.ip6
|
||
refute host.primary_interface.managed?
|
||
... | ... | |
assert_difference 'Host.count' do
|
||
Setting[:ignore_facts_for_domain] = true
|
||
raw = read_json_fixture('facts/facts_with_certname.json')
|
||
host = Host.import_host(raw['name'], 'puppet', raw['certname'])
|
||
host = Host.import_host(raw['name'], raw['certname'])
|
||
assert host.import_facts(raw['facts'])
|
||
assert Host.find_by_name('sinn1636.lan')
|
||
assert host.domain
|
||
... | ... | |
|
||
test 'should downcase hostname parameter from json of a new host' do
|
||
raw = read_json_fixture('facts/facts_with_caps.json')
|
||
host = Host.import_host(raw['name'], 'puppet')
|
||
host = Host.import_host(raw['name'])
|
||
assert host.import_facts(raw['facts'])
|
||
assert Host.find_by_name('sinn1636.lan')
|
||
end
|
||
|
||
test 'should downcase domain parameter from json of a new host' do
|
||
raw = read_json_fixture('facts/facts_with_caps.json')
|
||
host = Host.import_host(raw['name'], 'puppet')
|
||
host = Host.import_host(raw['name'])
|
||
assert host.import_facts(raw['facts'])
|
||
assert_equal raw['facts']['domain'].downcase, Host.find_by_name('sinn1636.lan').facts_hash['domain']
|
||
end
|
||
|
||
test 'should import facts idempotently' do
|
||
raw = read_json_fixture('facts/facts_with_caps.json')
|
||
host = Host.import_host(raw['name'], 'puppet')
|
||
host = Host.import_host(raw['name'])
|
||
assert host.import_facts(raw['facts'])
|
||
value_ids = Host.find_by_name('sinn1636.lan').fact_values.map(&:id)
|
||
assert host.import_facts(raw['facts'])
|
||
... | ... | |
# 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 = read_json_fixture('facts/facts_with_certname.json')
|
||
host = Host.import_host(raw['name'], 'puppet', raw['certname'])
|
||
host = Host.import_host(raw['name'], raw['certname'])
|
||
assert host.import_facts(raw['facts'])
|
||
host = Host.find_by_name('sinn1636.fail')
|
||
assert_equal '10.35.27.2', host.interfaces.find_by_identifier('br180').ip
|
||
... | ... | |
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 = read_json_fixture('facts/facts_with_certname.json')
|
||
host = Host.import_host(raw['name'], 'puppet', raw['certname'])
|
||
host = Host.import_host(raw['name'], raw['certname'])
|
||
assert host.import_facts(raw['facts'])
|
||
assert_equal 'sinn1636.lan.cert', Host.find_by_name('sinn1636.lan').certname
|
||
end
|
||
... | ... | |
assert_difference 'Host.count' do
|
||
Setting[:create_new_host_when_facts_are_uploaded] = true
|
||
raw = read_json_fixture('facts/facts_with_certname.json')
|
||
host = Host.import_host(raw['name'], 'puppet', raw['certname'])
|
||
host = Host.import_host(raw['name'], raw['certname'])
|
||
assert host.import_facts(raw['facts'])
|
||
assert Host.find_by_name('sinn1636.lan')
|
||
Setting[:create_new_host_when_facts_are_uploaded] =
|
||
... | ... | |
Setting[:create_new_host_when_facts_are_uploaded] = false
|
||
refute Setting[:create_new_host_when_facts_are_uploaded]
|
||
raw = read_json_fixture('facts/facts_with_certname.json')
|
||
host = Host.import_host(raw['name'], 'puppet', raw['certname'])
|
||
host = Host.import_host(raw['name'], raw['certname'])
|
||
refute host.import_facts(raw['facts'])
|
||
host = Host.find_by_name('sinn1636.lan')
|
||
Setting[:create_new_host_when_facts_are_uploaded] =
|
||
... | ... | |
Setting[:create_new_host_when_facts_are_uploaded] = false
|
||
refute Setting[:create_new_host_when_facts_are_uploaded]
|
||
raw = read_json_fixture('facts/facts_with_certname.json')
|
||
host = Host.import_host(raw['name'], 'puppet', raw['certname'])
|
||
host = Host.import_host(raw['name'], raw['certname'])
|
||
assert host.import_facts(raw['facts'])
|
||
end
|
||
|
||
test 'host taxonomies are set to a default when uploading facts' do
|
||
Setting[:create_new_host_when_facts_are_uploaded] = true
|
||
raw = read_json_fixture('facts/facts.json')
|
||
host = Host.import_host(raw['name'], 'puppet')
|
||
host = Host.import_host(raw['name'])
|
||
assert host.import_facts(raw['facts'])
|
||
|
||
assert_equal Setting[:default_location], Host.find_by_name('sinn1636.lan').location.title
|
||
... | ... | |
raw = read_json_fixture('facts/facts.json')
|
||
raw['facts']['foreman_location'] = 'Location 2'
|
||
raw['facts']['foreman_organization'] = 'Organization 2'
|
||
host = Host.import_host(raw['name'], 'puppet')
|
||
host = Host.import_host(raw['name'])
|
||
assert host.import_facts(raw['facts'])
|
||
|
||
assert_equal 'Location 2', Host.find_by_name('sinn1636.lan').location.title
|
||
... | ... | |
test 'default taxonomies are not assigned to hosts with taxonomies' do
|
||
Setting[:default_location] = taxonomies(:location1).title
|
||
raw = read_json_fixture('facts/facts.json')
|
||
host = Host.import_host(raw['name'], 'puppet')
|
||
host = Host.import_host(raw['name'])
|
||
assert host.import_facts(raw['facts'])
|
||
Host.find_by_name('sinn1636.lan').update_attribute(:location, taxonomies(:location2))
|
||
Host.find_by_name('sinn1636.lan').import_facts(raw['facts'])
|
||
... | ... | |
|
||
raw = read_json_fixture('facts/facts.json')
|
||
raw['facts']['foreman_location'] = 'Location 2'
|
||
host = Host.import_host(raw['name'], 'puppet')
|
||
host = Host.import_host(raw['name'])
|
||
assert host.import_facts(raw['facts'])
|
||
|
||
Host.find_by_name('sinn1636.lan').update_attribute(:location, taxonomies(:location1))
|
||
... | ... | |
end
|
||
|
||
test 'operatingsystem updated from facts' do
|
||
host = Host.import_host('host', 'puppet')
|
||
host = Host.import_host('host')
|
||
assert host.import_facts(:lsbdistrelease => '6.7', :operatingsystem => 'CentOS')
|
||
assert_equal 'CentOS 6.7', host.operatingsystem.to_s
|
||
end
|
||
|
||
test 'operatingsystem not updated from facts when ignore_facts_for_operatingsystem false' do
|
||
host = Host.import_host('host', 'puppet')
|
||
host = Host.import_host('host')
|
||
assert host.import_facts(:lsbdistrelease => '6.7', :operatingsystem => 'CentOS')
|
||
Setting[:ignore_facts_for_operatingsystem] = true
|
||
assert host.import_facts(:lsbdistrelease => '6.8', :operatingsystem => 'CentOS')
|
||
... | ... | |
assert_difference 'Host.count' do
|
||
Setting[:ignore_facts_for_operatingsystem] = true
|
||
raw = read_json_fixture('facts/facts_with_certname.json')
|
||
host = Host.import_host(raw['name'], 'puppet', raw['certname'])
|
||
host = Host.import_host(raw['name'], raw['certname'])
|
||
assert host.import_facts(raw['facts'])
|
||
assert Host.find_by_name('sinn1636.lan')
|
||
assert host.operatingsystem
|
||
... | ... | |
:fact_name => FactoryGirl.create(:fact_name, :name => 'kernelversion'))
|
||
assert_difference('Model.count') do
|
||
facts = read_json_fixture('facts/facts.json')
|
||
h.populate_fields_from_facts facts['facts']
|
||
h.populate_fields_from_facts(
|
||
mock_parser(facts['facts']),
|
||
'puppet',
|
||
nil)
|
||
end
|
||
end
|
||
|
||
... | ... | |
test "should update puppet_proxy_id to the id of the validated proxy" do
|
||
sp = smart_proxies(:puppetmaster)
|
||
raw = read_json_fixture('facts/facts_with_caps.json')
|
||
host = Host.import_host(raw['name'], 'puppet', nil, sp.id)
|
||
assert host.import_facts(raw['facts'])
|
||
host = Host.import_host(raw['name'], nil)
|
||
assert host.import_facts(raw['facts'], sp)
|
||
assert_equal sp.id, Host.find_by_name('sinn1636.lan').puppet_proxy_id
|
||
end
|
||
|
||
test "should not update puppet_proxy_id if it was not puppet upload" do
|
||
sp = smart_proxies(:puppetmaster)
|
||
raw = read_json_fixture('facts/facts_with_caps.json')
|
||
host = Host.import_host(raw['name'], 'chef', nil, sp.id)
|
||
host = Host.import_host(raw['name'])
|
||
assert host.import_facts(raw['facts'].merge(:_type => 'chef'), sp)
|
||
assert_nil host.puppet_proxy_id
|
||
end
|
||
|
||
... | ... | |
Host.new(:name => 'sinn1636.lan', :puppet_proxy_id => smart_proxies(:puppetmaster).id).save(:validate => false)
|
||
sp = smart_proxies(:puppetmaster)
|
||
raw = read_json_fixture('facts/facts_with_certname.json')
|
||
host = Host.import_host(raw['name'], 'puppet', nil, sp.id)
|
||
assert host.import_facts(raw['facts'])
|
||
host = Host.import_host(raw['name'])
|
||
assert host.import_facts(raw['facts'], sp)
|
||
assert_equal smart_proxies(:puppetmaster).id, Host.find_by_name('sinn1636.lan').puppet_proxy_id
|
||
end
|
||
|
||
... | ... | |
test 'check operatingsystem and architecture association' do
|
||
host = FactoryGirl.build(:host, :interfaces => [FactoryGirl.build(:nic_primary_and_provision)])
|
||
assert_nil Operatingsystem.find_by_name('RedHat-test'), "operatingsystem already exist"
|
||
host.populate_fields_from_facts(:architecture => "x86_64", :operatingsystem => 'RedHat-test', :operatingsystemrelease => '6.2')
|
||
host.populate_fields_from_facts(
|
||
mock_parser(:architecture => "x86_64", :operatingsystem => 'RedHat-test', :operatingsystemrelease => '6.2'),
|
||
'puppet',
|
||
nil)
|
||
assert host.operatingsystem.architectures.include?(host.architecture), "no association between operatingsystem and architecture"
|
||
end
|
||
|
||
... | ... | |
parser = stub(:ipmi_interface => hash, :interfaces => {}, :suggested_primary_interface => [ primary.identifier, {:macaddress => primary.mac, :ipaddress => primary.ip} ])
|
||
[host, parser]
|
||
end
|
||
|
||
def mock_parser(properties)
|
||
PuppetFactParser.new(properties)
|
||
end
|
||
end
|
test/unit/facet_test.rb | ||
---|---|---|
|
||
@host.apply_inherited_attributes(attributes)
|
||
end
|
||
|
||
test 'facts are parsed by facets too' do
|
||
TestFacet.expects(:populate_fields_from_facts)
|
||
@host.stubs(:save)
|
||
facts_json = read_json_fixture('facts/brslc022.facts.json')
|
||
|
||
@host.parse_facts(facts_json['facts'], nil, nil)
|
||
end
|
||
end
|
||
|
||
context "managed host facet behavior" do
|
test/unit/fact_parser_test.rb | ||
---|---|---|
test ".register_custom_parser" do
|
||
chef_parser = Struct.new(:my_method)
|
||
FactParser.register_fact_parser :chef, chef_parser
|
||
|
||
assert_equal chef_parser, FactParser.parser_for(:chef)
|
||
begin
|
||
assert_equal chef_parser, FactParser.parser_for(:chef)
|
||
ensure
|
||
FactParser.parsers.delete :chef
|
||
end
|
||
end
|
||
|
||
test "#parse_interfaces? should answer based on current setttings" do
|
Also available in: Unified diff
Fixes #15409 - Separated puppet facts from core