app/services/foreman_chef/fact_parser.rb
module ForemanChef
class FactParser < ::FactParser
VIRTUAL = /\A([a-z0-9]+)[:.](\d+)\Z/
VIRTUAL_NAMES = /#{VIRTUAL}|#{BRIDGES}|#{BONDS}/
def operatingsystem
os_name = facts['lsb::id'] || facts['platform']
release = facts['lsb::release'] || facts['platform_version']
# if we have no release information we can't assign OS properly (e.g. missing redhat-lsb)
if release.nil?
major, minor = 1, nil
elsif os_name == 'Ubuntu'
major, minor = release, nil
else
major, minor = release.split('.')
end
if facts['platform'] == 'windows'
release_name = facts['kernel::name']
os_name = os_name.capitalize
else
# to keep OS names consistent with other cfgmgt agents we skip description importing
# description = facts['lsb::description']
release_name = facts['lsb::codename']
end
# convert OHAI names to Foreman common OS names
case os_name
when 'RedHatEnterpriseServer'
os_name = 'RedHat'
end
# So far only CentOS needs exception
case os_name
when 'CentOS'
if major.to_i >= 7
# get the minor.build number, e.g. 7.2.1511 -> 2.1511
minor = release[2..-1]
end
when 'centos'
# Centos Stream doesn't have minor version on need to replace blank spaces due to name restriction
# Also normalize CentOS / CentOS Linux
os_name = minor ? 'CentOS' : facts.dig(:os_release, :name).tr(' ', '_')
end
begin
klass = os_name.constantize
rescue NameError => _
logger.debug "unknown operating system #{os_name}, fallback to generic type"
klass = Operatingsystem
end
args = { :name => os_name, :major => major.to_s, :minor => minor.to_s }
klass.where(args).first || klass.new(args.merge(:release_name => release_name)).tap(&:save!)
end
# we don't want to parse puppet environment, foreman_chef already has its own environment
def environment
nil
end
def architecture
name = facts['kernel::machine']
name = "x86_64" if name == "amd64"
Architecture.where(:name => name).first_or_create if name.present?
end
def model
if facts['virtualization'].present?
name = facts['virtualization']['system']
else
name = facts['dmi::system::product_name']
end
Model.where(:name => name.strip).first_or_create if name.present?
end
def domain
name = facts['domain']
Domain.where(:name => name).first_or_create if name.present?
end
def ipmi_interface
if facts['ipmi::mac_address'].present?
{ 'ipaddress' => facts['ipmi::address'], 'macaddress' => facts['ipmi::mac_address'] }.with_indifferent_access
end
end
def certname
facts['chef_node_name']
end
def support_interfaces_parsing?
true
end
def parse_interfaces?
support_interfaces_parsing? && !Setting['ignore_puppet_facts_for_provisioning']
end
def boot_timestamp
Time.zone.now.to_i - facts['system_uptime::seconds'].to_i
end
def bios
{
:vendor => facts['dmi::bios::all_records::vendor'],
:version => facts['dmi::bios::all_records::version'],
:release_date => facts['dmi::bios::all_records::release_date'],
}
end
private
def logger
Foreman::Logging.logger('background')
end
# meant to be implemented in inheriting classes
# should return hash with indifferent access in following format:
# { 'link': 'true',
# 'macaddress': '00:00:00:00:00:FF',
# 'ipaddress': nil,
# 'any_other_fact': 'value' }
#
# note that link and macaddress are mandatory
def get_facts_for_interface(interface)
facts = interfaces_hash[interface]
hash = {
'link' => facts['state'] == 'up',
'macaddress' => get_address_by_family(facts['addresses'], 'lladdr'),
'ipaddress' => get_address_by_family(facts['addresses'], 'inet'),
}
hash['tag'] = facts['vlan']['id'] if facts['vlan'].present?
hash.with_indifferent_access
end
# meant to be implemented in inheriting classes
# should return array of interfaces names, e.g.
# ['eth0', 'eth0.0', 'eth1']
def get_interfaces
interfaces_hash.keys
end
def get_address_by_family(addresses, family)
addresses.detect { |address, attributes| attributes['family'] == family }.try(:first)
end
def network_hash
@network_hash ||= ForemanChef::FactImporter::Sparser.new.unsparse(facts.select { |k, v| k.start_with?('network::interfaces::') }).try(:[], 'network') || {}
end
def interfaces_hash
@interfaces_hash ||= network_hash.try(:[], 'interfaces') || {}
end
# adds attributes like virtual
def set_additional_attributes(attributes, name)
if name =~ VIRTUAL_NAMES
attributes[:virtual] = true
if Regexp.last_match(1).nil? && name =~ BRIDGES
attributes[:bridge] = true
else
attributes[:attached_to] = Regexp.last_match(1)
end
else
attributes[:virtual] = false
end
attributes
end
end
end