facter 4.0.52 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/facter.rb +20 -20
  3. data/lib/facter/custom_facts/core/execution/base.rb +7 -3
  4. data/lib/facter/custom_facts/core/execution/popen3.rb +13 -1
  5. data/lib/facter/custom_facts/util/collection.rb +5 -0
  6. data/lib/facter/custom_facts/util/normalization.rb +7 -2
  7. data/lib/facter/facts/linux/cloud/provider.rb +9 -2
  8. data/lib/facter/facts/linux/hypervisors/xen.rb +2 -4
  9. data/lib/facter/facts/solaris/hypervisors/ldom.rb +1 -1
  10. data/lib/facter/facts/solaris/hypervisors/zone.rb +1 -1
  11. data/lib/facter/facts/solaris/mountpoints.rb +1 -1
  12. data/lib/facter/facts/windows/az_metadata.rb +1 -5
  13. data/lib/facter/facts/windows/cloud/provider.rb +6 -2
  14. data/lib/facter/framework/core/fact_augmenter.rb +21 -4
  15. data/lib/facter/framework/core/fact_loaders/external_fact_loader.rb +9 -6
  16. data/lib/facter/framework/core/fact_loaders/fact_loader.rb +36 -33
  17. data/lib/facter/framework/core/fact_manager.rb +85 -14
  18. data/lib/facter/framework/core/options/config_file_options.rb +7 -0
  19. data/lib/facter/framework/core/options/option_store.rb +3 -1
  20. data/lib/facter/framework/formatters/formatter_helper.rb +3 -5
  21. data/lib/facter/framework/parsers/query_parser.rb +7 -14
  22. data/lib/facter/models/fact_collection.rb +32 -5
  23. data/lib/facter/resolvers/aix/ffi/ffi_helper.rb +1 -1
  24. data/lib/facter/resolvers/base_resolver.rb +2 -2
  25. data/lib/facter/resolvers/dmi_decode.rb +0 -1
  26. data/lib/facter/resolvers/linux/hostname.rb +16 -5
  27. data/lib/facter/resolvers/macosx/mountpoints.rb +14 -1
  28. data/lib/facter/resolvers/networking.rb +1 -1
  29. data/lib/facter/resolvers/selinux.rb +5 -7
  30. data/lib/facter/resolvers/solaris/ffi/structs.rb +12 -0
  31. data/lib/facter/resolvers/solaris/mountpoints.rb +22 -16
  32. data/lib/facter/resolvers/solaris/networking.rb +20 -5
  33. data/lib/facter/resolvers/solaris/zone.rb +0 -1
  34. data/lib/facter/resolvers/windows/uptime.rb +3 -22
  35. data/lib/facter/templates/man.erb +6 -6
  36. data/lib/facter/util/facts/unit_converter.rb +2 -2
  37. data/lib/facter/util/facts/virtual_detector.rb +6 -13
  38. data/lib/facter/util/resolvers/http.rb +7 -1
  39. data/lib/facter/util/resolvers/networking/primary_interface.rb +11 -5
  40. data/lib/facter/util/utils.rb +18 -1
  41. data/lib/facter/version.rb +1 -1
  42. metadata +3 -3
@@ -8,18 +8,16 @@ module Facter
8
8
  @internal_fact_mgr = InternalFactManager.new
9
9
  @external_fact_mgr = ExternalFactManager.new
10
10
  @fact_loader = FactLoader.instance
11
+ @options = Options.get
11
12
  @log = Log.new(self)
12
13
  end
13
14
 
14
- def searched_facts(user_query = [])
15
- loaded_facts = @fact_loader.load(Options.get)
16
- QueryParser.parse(user_query, loaded_facts)
17
- end
18
-
19
15
  def resolve_facts(user_query = [])
20
- searched_facts = parse_user_query(user_query)
21
-
16
+ @options[:user_query] = user_query
22
17
  cache_manager = Facter::CacheManager.new
18
+
19
+ searched_facts = QueryParser.parse(user_query, @fact_loader.load(@options))
20
+
23
21
  searched_facts, cached_facts = cache_manager.resolve_facts(searched_facts)
24
22
  internal_facts = @internal_fact_mgr.resolve_facts(searched_facts)
25
23
  external_facts = @external_fact_mgr.resolve_facts(searched_facts)
@@ -35,23 +33,96 @@ module Facter
35
33
  resolved_facts
36
34
  end
37
35
 
38
- def resolve_core(user_query = [])
39
- loaded_facts_hash = @fact_loader.internal_facts
36
+ # resolve a fact by name, in a similar way that facter 3 does.
37
+ # search is done in multiple steps, and the next step is executed
38
+ # only if the previous one was not able to resolve the fact
39
+ # - load the `fact_name.rb` from the configured custom directories
40
+ # - load all the core facts, external facts and env facts
41
+ # - load all custom facts
42
+ def resolve_fact(user_query)
43
+ @options[:user_query] = user_query
44
+ @log.debug("resolving fact with user_query: #{user_query}")
45
+
46
+ @cache_manager = Facter::CacheManager.new
47
+
48
+ custom_facts = custom_fact_by_filename || []
49
+ core_and_external_facts = core_or_external_fact || []
50
+ resolved_facts = core_and_external_facts + custom_facts
51
+
52
+ resolved_facts = all_custom_facts if resolved_facts.empty?
53
+
54
+ @cache_manager.cache_facts(resolved_facts)
55
+
56
+ log_resolved_facts(resolved_facts)
57
+ resolved_facts
58
+ end
59
+
60
+ def resolve_core(user_query = [], options = {})
61
+ @cache_manager = CacheManager.new
62
+ core_fact(user_query, options)
63
+ end
64
+
65
+ private
66
+
67
+ def core_fact(user_query, options)
68
+ loaded_facts_hash = @fact_loader.load_internal_facts(options)
40
69
 
41
70
  searched_facts = QueryParser.parse(user_query, loaded_facts_hash)
71
+ searched_facts, cached_facts = @cache_manager.resolve_facts(searched_facts)
72
+
42
73
  resolved_facts = @internal_fact_mgr.resolve_facts(searched_facts)
74
+ resolved_facts = resolved_facts.concat(cached_facts)
75
+
43
76
  FactFilter.new.filter_facts!(resolved_facts, user_query)
44
77
 
45
78
  resolved_facts
46
79
  end
47
80
 
48
- private
81
+ def custom_fact_by_filename
82
+ user_query = @options[:user_query]
83
+ @log.debug("Searching fact: #{user_query} in file: #{user_query}.rb")
84
+
85
+ custom_fact = @fact_loader.load_custom_fact(@options, user_query)
86
+ return unless custom_fact.any?
87
+
88
+ searched_facts = parse_user_query(custom_fact, @options)
89
+ searched_facts, cached_facts = @cache_manager.resolve_facts(searched_facts)
90
+
91
+ resolved_facts = @external_fact_mgr.resolve_facts(searched_facts)
92
+ resolved_facts = resolved_facts.concat(cached_facts)
93
+ resolved_facts if resolved_facts.any?
94
+ end
49
95
 
50
- def parse_user_query(user_query)
51
- options = Options.get
52
- options[:user_query] = user_query
53
- loaded_facts = @fact_loader.load(options)
96
+ def core_or_external_fact
97
+ user_query = @options[:user_query]
98
+ @log.debug("Searching fact: #{user_query} in core facts and external facts")
99
+
100
+ core_facts = core_fact([user_query], @options)
101
+ external_facts = @fact_loader.load_external_facts(@options)
102
+ searched_facts = parse_user_query(external_facts, @options)
103
+ searched_facts, cached_facts = @cache_manager.resolve_facts(searched_facts)
104
+
105
+ resolved_facts = @external_fact_mgr.resolve_facts(searched_facts)
106
+ resolved_facts = override_core_facts(core_facts, resolved_facts)
107
+ resolved_facts = resolved_facts.concat(cached_facts)
108
+
109
+ resolved_facts unless resolved_facts.map(&:value).compact.empty?
110
+ end
111
+
112
+ def all_custom_facts
113
+ user_query = @options[:user_query]
114
+ @log.debug("Searching fact: #{user_query} in all custom facts")
115
+
116
+ custom_facts = @fact_loader.load_custom_facts(@options)
117
+ searched_facts = parse_user_query(custom_facts, @options)
118
+ searched_facts, cached_facts = @cache_manager.resolve_facts(searched_facts)
119
+
120
+ resolved_facts = @external_fact_mgr.resolve_facts(searched_facts)
121
+ resolved_facts.concat(cached_facts)
122
+ end
54
123
 
124
+ def parse_user_query(loaded_facts, options)
125
+ user_query = Array(options[:user_query])
55
126
  QueryParser.parse(user_query, loaded_facts)
56
127
  end
57
128
 
@@ -18,6 +18,12 @@ module Facter
18
18
 
19
19
  private
20
20
 
21
+ def augment_structured_facts(global_conf)
22
+ return if !global_conf || global_conf['force-dot-resolution'].nil?
23
+
24
+ @options[:force_dot_resolution] = global_conf['force-dot-resolution']
25
+ end
26
+
21
27
  def augment_all
22
28
  augment_cli(Facter::ConfigReader.cli) if Options.cli?
23
29
  augment_globals
@@ -27,6 +33,7 @@ module Facter
27
33
  def augment_globals
28
34
  augment_ruby(Facter::ConfigReader.global)
29
35
 
36
+ augment_structured_facts(Facter::ConfigReader.global)
30
37
  augment_custom(Facter::ConfigReader.global)
31
38
  augment_external(Facter::ConfigReader.global)
32
39
  augment_show_legacy(Facter::ConfigReader.global)
@@ -35,6 +35,7 @@ module Facter
35
35
  @custom_dir = []
36
36
  @hocon = false
37
37
  @allow_external_loggers = true
38
+ @force_dot_resolution = false
38
39
 
39
40
  class << self
40
41
  attr_reader :debug, :verbose, :log_level, :show_legacy,
@@ -42,7 +43,7 @@ module Facter
42
43
 
43
44
  attr_accessor :config, :strict, :json,
44
45
  :cache, :yaml, :puppet, :ttls, :block, :cli, :config_file_custom_dir,
45
- :config_file_external_dir, :default_external_dir, :fact_groups,
46
+ :config_file_external_dir, :default_external_dir, :fact_groups, :force_dot_resolution,
46
47
  :block_list, :color, :trace, :sequential, :timing, :hocon, :allow_external_loggers
47
48
 
48
49
  attr_writer :external_dir
@@ -202,6 +203,7 @@ module Facter
202
203
 
203
204
  def reset_facts
204
205
  @custom_facts = true
206
+ @force_dot_resolution = false
205
207
  @external_dir = []
206
208
  @custom_dir = []
207
209
  end
@@ -4,12 +4,11 @@ module Facter
4
4
  class FormatterHelper
5
5
  class << self
6
6
  def retrieve_facts_to_display_for_user_query(user_queries, resolved_facts)
7
- facts_to_display = {}
7
+ facts_to_display = FactCollection.new
8
8
  user_queries.each do |user_query|
9
9
  fact_collection = build_fact_collection_for_user_query(user_query, resolved_facts)
10
10
 
11
- splitted_user_query = Utils.split_user_query(user_query)
12
- printable_value = fact_collection.dig(*splitted_user_query)
11
+ printable_value = fact_collection.dig_fact(user_query)
13
12
  facts_to_display.merge!(user_query => printable_value)
14
13
  end
15
14
 
@@ -24,8 +23,7 @@ module Facter
24
23
  def retrieve_fact_value_for_single_query(user_query, resolved_facts)
25
24
  fact_collection = build_fact_collection_for_user_query(user_query, resolved_facts)
26
25
  fact_collection = Utils.sort_hash_by_key(fact_collection)
27
- splitted_user_query = Utils.split_user_query(user_query)
28
- fact_collection.dig(*splitted_user_query)
26
+ fact_collection.dig_fact(user_query)
29
27
  end
30
28
 
31
29
  private
@@ -69,19 +69,12 @@ module Facter
69
69
  resolvable_fact_list = []
70
70
 
71
71
  loaded_fact_hash.each do |loaded_fact|
72
- # return the fact if toplevel namespace is found in the first query token
73
- # eg. query: 'os.name' and loaded_fact_hash contains a fact with 'os' name
74
- # it will construt and return the 'os' fact
75
- if loaded_fact.name == query_tokens[0]
76
- resolvable_fact_list = [construct_loaded_fact(query_tokens, query_token_range, loaded_fact)]
77
- else
78
- query_fact = query_tokens[query_token_range].join('.')
79
-
80
- next unless found_fact?(loaded_fact.name, query_fact)
81
-
82
- searched_fact = construct_loaded_fact(query_tokens, query_token_range, loaded_fact)
83
- resolvable_fact_list << searched_fact
84
- end
72
+ query_fact = query_tokens[query_token_range].join('.')
73
+
74
+ next unless found_fact?(loaded_fact.name, query_fact)
75
+
76
+ searched_fact = construct_loaded_fact(query_tokens, query_token_range, loaded_fact)
77
+ resolvable_fact_list << searched_fact
85
78
  end
86
79
 
87
80
  @log.debug "List of resolvable facts: #{resolvable_fact_list.inspect}"
@@ -113,7 +106,7 @@ module Facter
113
106
  end
114
107
 
115
108
  def construct_filter_tokens(query_tokens, query_token_range)
116
- (query_tokens - query_tokens[query_token_range]).map do |token|
109
+ query_tokens.drop(query_token_range.size).map do |token|
117
110
  token =~ /^[0-9]+$/ ? token.to_i : token
118
111
  end
119
112
  end
@@ -7,6 +7,10 @@ module Facter
7
7
  @log = Log.new(self)
8
8
  end
9
9
 
10
+ def to_yaml
11
+ deep_to_h.to_yaml
12
+ end
13
+
10
14
  def build_fact_collection!(facts)
11
15
  facts.each do |fact|
12
16
  next if %i[core legacy].include?(fact.type) && fact.value.nil?
@@ -17,10 +21,20 @@ module Facter
17
21
  self
18
22
  end
19
23
 
20
- def value(*keys)
21
- keys.reduce(self) do |memo, key|
22
- memo.fetch(key.to_s)
23
- end
24
+ def dig_fact(user_query)
25
+ split_user_query = Facter::Utils.split_user_query(user_query)
26
+ fact = dig(user_query) || dig(*split_user_query)
27
+ rescue TypeError
28
+ # An incorrect user query (e.g. mountpoints./.available.asd) can cause
29
+ # Facter to call dig on a string, which raises a type error.
30
+ # If this happens, we assume the query is wrong and silently continue.
31
+ ensure
32
+ @log.debug("Fact \"#{user_query}\" does not exist") unless fact
33
+ fact
34
+ end
35
+
36
+ def value(user_query)
37
+ dig_fact(user_query)
24
38
  end
25
39
 
26
40
  def bury(*args)
@@ -39,6 +53,12 @@ module Facter
39
53
 
40
54
  private
41
55
 
56
+ def deep_to_h(collection = self)
57
+ collection.each_pair.with_object({}) do |(key, value), hash|
58
+ hash[key] = value.is_a?(FactCollection) ? deep_to_h(value) : value
59
+ end
60
+ end
61
+
42
62
  def bury_fact(fact)
43
63
  split_fact_name = extract_fact_name(fact)
44
64
  bury(*split_fact_name + fact.filter_tokens << fact.value)
@@ -49,7 +69,14 @@ module Facter
49
69
  end
50
70
 
51
71
  def extract_fact_name(fact)
52
- fact.type == :legacy ? [fact.name] : fact.name.split('.')
72
+ case fact.type
73
+ when :legacy
74
+ [fact.name]
75
+ when :custom, :external
76
+ Options[:force_dot_resolution] == true ? fact.name.split('.') : [fact.name]
77
+ else
78
+ fact.name.split('.')
79
+ end
53
80
  end
54
81
  end
55
82
  end
@@ -33,7 +33,7 @@ module Facter
33
33
 
34
34
  return if Libc.getkerninfo(KINFO_READ | KINFO_GET_AVENRUN, averages, averages_size, 0).negative?
35
35
 
36
- averages.read_array_of_long_long(3).map { |x| (x / 65_536.0).round(5) }
36
+ averages.read_array_of_long_long(3).map { |x| (x / 65_536.0) }
37
37
  end
38
38
 
39
39
  def self.read_interfaces
@@ -28,10 +28,10 @@ module Facter
28
28
  cache_nil_for_unresolved_facts(fact_name)
29
29
  end
30
30
  rescue NoMethodError => e
31
- log.debug("Could not resolve #{fact_name}, got #{e}")
31
+ log.debug("Could not resolve #{fact_name}, got #{e} at #{e.backtrace[0]}")
32
32
  @fact_list[fact_name] = nil
33
33
  rescue LoadError, NameError => e
34
- log.debug("resolving fact #{fact_name}, but #{e}")
34
+ log.debug("Resolving fact #{fact_name}, but got #{e} at #{e.backtrace[0]}")
35
35
  @fact_list[fact_name] = nil
36
36
  end
37
37
 
@@ -37,7 +37,6 @@ module Facter
37
37
  @fact_list[:virtualbox_version] = output.match(/vboxVer_(\S+)/)&.captures&.first
38
38
  @fact_list[:virtualbox_revision] = output.match(/vboxRev_(\S+)/)&.captures&.first
39
39
  @fact_list[:vmware_version] = extract_vmware_version(output)
40
- @fact_list[:vendor] = output.match(/Vendor: (\S+)/)&.captures&.first
41
40
 
42
41
  @fact_list[fact_name]
43
42
  end
@@ -19,7 +19,6 @@ module Facter
19
19
 
20
20
  def retrieve_info(fact_name)
21
21
  require 'socket'
22
- require 'facter/util/resolvers/ffi/hostname'
23
22
 
24
23
  output = retrieving_hostname
25
24
  return nil unless output
@@ -38,13 +37,20 @@ module Facter
38
37
  end
39
38
 
40
39
  def retrieving_hostname
41
- output = Socket.gethostname
42
- if !output || output.empty? || output['0.0.0.0']
43
- output = Facter::Util::Resolvers::Ffi::Hostname.getffihostname
40
+ output = Socket.gethostname || ''
41
+ if output.empty? || output['0.0.0.0']
42
+ begin
43
+ require 'facter/util/resolvers/ffi/hostname'
44
+
45
+ output = Facter::Util::Resolvers::Ffi::Hostname.getffihostname
46
+ rescue LoadError => e
47
+ log.debug(e.message)
48
+ output = nil
49
+ end
44
50
  end
45
51
 
46
52
  log.debug("Tried to retrieve hostname and got: #{output}")
47
- output && !output.empty? ? output : nil
53
+ return output unless output&.empty?
48
54
  end
49
55
 
50
56
  def parse_fqdn(output)
@@ -70,9 +76,14 @@ module Facter
70
76
  end
71
77
 
72
78
  def retrieve_fqdn_for_host_with_ffi(host)
79
+ require 'facter/util/resolvers/ffi/hostname'
80
+
73
81
  fqdn = Facter::Util::Resolvers::Ffi::Hostname.getffiaddrinfo(host)
74
82
  log.debug("FFI getaddrinfo was called and it retrieved: #{fqdn}")
75
83
  fqdn
84
+ rescue LoadError => e
85
+ log.debug(e.message)
86
+ nil
76
87
  end
77
88
 
78
89
  def exists_and_valid_fqdn?(fqdn, hostname)
@@ -21,7 +21,7 @@ module Facter
21
21
  device = fs.name
22
22
  filesystem = fs.mount_type
23
23
  path = fs.mount_point
24
- options = fs.options.split(',').map(&:strip).map { |o| o == 'rootfs' ? 'root' : o }
24
+ options = read_options(fs.options)
25
25
 
26
26
  mounts[path] = read_stats(path).tap do |hash|
27
27
  hash[:device] = device
@@ -53,6 +53,19 @@ module Facter
53
53
  used: Facter::Util::Facts::UnitConverter.bytes_to_human_readable(used_bytes)
54
54
  }
55
55
  end
56
+
57
+ def read_options(options)
58
+ options_map = {
59
+ 'read-only' => 'readonly',
60
+ 'asynchronous' => 'async',
61
+ 'synchronous' => 'noasync',
62
+ 'quotas' => 'quota',
63
+ 'rootfs' => 'root',
64
+ 'defwrite' => 'deferwrites'
65
+ }
66
+
67
+ options.split(',').map(&:strip).map { |o| options_map.key?(o) ? options_map[o] : o }
68
+ end
56
69
  end
57
70
  end
58
71
  end
@@ -42,7 +42,7 @@ module Facter
42
42
 
43
43
  def parse_interfaces_response(response)
44
44
  parsed_interfaces_data = {}
45
- interfaces_data = Hash[*response.split(/^([A-Za-z0-9_]+): /)[1..-1]]
45
+ interfaces_data = Hash[*response.split(/^([A-Za-z0-9_\.]+): /)[1..-1]]
46
46
 
47
47
  interfaces_data.each do |interface_name, raw_data|
48
48
  parsed_interface_data = {}
@@ -13,21 +13,21 @@ module Facter
13
13
  end
14
14
 
15
15
  def retrieve_facts(fact_name)
16
- mountpoint = read_mounts_file if @fact_list[:enabled].nil?
16
+ mountpoint = selinux_mountpoint
17
+
18
+ @fact_list[:enabled] = !mountpoint.empty? && read_selinux_config
17
19
  read_other_selinux_facts(mountpoint) if @fact_list[:enabled]
18
20
 
19
21
  @fact_list[fact_name]
20
22
  end
21
23
 
22
- def read_mounts_file
24
+ def selinux_mountpoint
23
25
  output = Facter::Core::Execution.execute('cat /proc/self/mounts', logger: log)
24
- @fact_list[:enabled] = false
25
26
  mountpoint = ''
26
27
 
27
28
  output.each_line do |line|
28
29
  next unless line =~ /selinuxfs/
29
30
 
30
- @fact_list[:enabled] = true
31
31
  mountpoint = line.split("\s")[1]
32
32
  break
33
33
  end
@@ -35,8 +35,6 @@ module Facter
35
35
  end
36
36
 
37
37
  def read_other_selinux_facts(mountpoint)
38
- return unless read_selinux_config
39
-
40
38
  enforce_file = "#{mountpoint}/enforce"
41
39
  policy_file = "#{mountpoint}/policyvers"
42
40
 
@@ -60,7 +58,7 @@ module Facter
60
58
  @fact_list[:config_policy] = line.split('=').last.strip if line =~ /^SELINUXTYPE=/
61
59
  end
62
60
 
63
- true unless file_lines.empty?
61
+ !file_lines.empty? ? true : false
64
62
  end
65
63
  end
66
64
  end