/
fact_parser.rb
190 lines (157 loc) · 5.44 KB
/
fact_parser.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
class FactParser
delegate :logger, :to => :Rails
VIRTUAL = /\A([a-z0-9]+)_(\d+)\Z/
BRIDGES = /\A(vir)?br\d+(_nic)?\Z/
BONDS = /\A(bond\d+)|(lagg\d+)\Z/
VIRTUAL_NAMES = /#{VIRTUAL}|#{BRIDGES}|#{BONDS}/
def self.parser_for(type)
parsers[type.to_s] || parsers[:puppet]
end
def self.parsers
@parsers ||= { :puppet => PuppetFactParser }.with_indifferent_access
end
def self.register_fact_importer(key, klass)
ActiveSupport::Deprecation.warn('FactParser#register_fact_importer was renamed to FactParser#register_fact_parser, please update your code')
register_fact_parser(key, klass)
end
def self.register_fact_parser(key, klass)
parsers[key.to_sym] = klass
end
attr_reader :facts
# facts are hash of fresh data coming from fact upload in following format
# { fqdn: value, hostname: value }
def initialize(facts)
@facts = HashWithIndifferentAccess.new(facts)
end
def operatingsystem
raise NotImplementedError, not_implemented_error(__method__)
end
def environment
raise NotImplementedError, not_implemented_error(__method__)
end
def architecture
raise NotImplementedError, not_implemented_error(__method__)
end
def model
raise NotImplementedError, not_implemented_error(__method__)
end
def domain
raise NotImplementedError, not_implemented_error(__method__)
end
def ipmi_interface
raise NotImplementedError, not_implemented_error(__method__)
end
# should return hash with indifferent access in following format:
# {
# 'eth0': {'link': 'true', 'macaddress': '00:00:00:00:00:FF', 'ipaddress': nil, 'any_other_fact': 'value'},
# 'eth0.0': { ... }
# }
def interfaces
@interfaces ||= begin
result = {}
interfaces = remove_ignored(normalize_interfaces(get_interfaces))
logger.debug "We have following interfaces '#{interfaces.join(', ')}' based on facts"
interfaces.each do |interface|
iface_facts = get_facts_for_interface(interface)
iface_facts = set_additional_attributes(iface_facts, interface)
result[interface] = iface_facts
end
result.with_indifferent_access
end
end
# tries to detect primary interface among interfaces using host name
def suggested_primary_interface(host)
# we search among interface with ip and mac if we didn't find it by name
potential = interfaces.select { |_, values| values[:ipaddress].present? && values[:macaddress].present? }
find_interface_by_name(host.name) || find_physical_interface(potential) ||
find_virtual_interface(potential) || potential.first || interfaces.first
end
def certname
raise NotImplementedError, not_implemented_error(__method__)
end
def support_interfaces_parsing?
false
end
def parse_interfaces?
support_interfaces_parsing? && !Setting['ignore_puppet_facts_for_provisioning']
end
private
def find_interface_by_name(host_name)
interfaces.detect do |int, values|
if (ip = values[:ipaddress]).present?
begin
if Resolv::DNS.new.getnames(ip).any? { |name| name.to_s == host_name }
logger.debug "resolved #{host_name} for #{ip}, #{int} is selected as primary"
return [int, values]
end
rescue Resolv::ResolvError => e
logger.debug "could not resolv name for #{ip} because of #{e} #{e.message}"
nil
end
end
end
end
def find_physical_interface(interfaces)
interfaces.detect { |int, _| int.to_s !~ FactParser::VIRTUAL_NAMES }
end
def find_virtual_interface(interfaces)
interfaces.detect { |int, _| int.to_s =~ /#{FactParser::BONDS}/ }
end
# adds attributes like virtual
def set_additional_attributes(attributes, name)
if name =~ VIRTUAL_NAMES
attributes[:virtual] = true
if $1.nil? && name =~ BRIDGES
attributes[:bridge] = true
else
attributes[:attached_to] = $1
if @facts[:vlans].present?
vlans = @facts[:vlans].split(',')
tag = name.split('_').last
attributes[:tag] = vlans.include?(tag) ? tag : ''
end
end
else
attributes[:virtual] = false
end
attributes
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)
raise NotImplementedError, "parsing interface facts is not supported in #{self.class}"
end
# meant to be implemented in inheriting classes
# should return array of interfaces names, e.g.
# ['eth0', 'eth0.0', 'eth1']
def get_interfaces
raise NotImplementedError, "parsing interfaces is not supported in #{self.class}"
end
# these interfaces are ignored when parsing interface facts
def ignored_interfaces
/\A(lo(?!cal_area_connection)|usb|vnet)/
end
def remove_ignored(interfaces)
interfaces.clone.delete_if { |i| i.match(ignored_interfaces) }
end
def normalize_interfaces(interfaces)
interfaces.map(&:downcase)
end
# creating if iface_facts[:link] == 'true' && Net::Validations.normalize_mac(iface_facts[:macaddress]) != @host.mac
def not_implemented_error(method)
"#{method} fact parsing not implemented in #{self.class}"
end
def is_numeric?(string)
begin
!!Integer(string)
rescue ArgumentError, TypeError
false
end
end
end