facter 4.5.0 → 4.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/facter/custom_facts/util/directory_loader.rb +6 -1
- data/lib/facter/facts/amzn/os/distro/release.rb +6 -1
- data/lib/facter/facts/amzn/os/release.rb +5 -0
- data/lib/facter/framework/core/fact_loaders/class_discoverer.rb +4 -3
- data/lib/facter/framework/core/file_loader.rb +2 -0
- data/lib/facter/framework/parsers/query_parser.rb +24 -20
- data/lib/facter/resolvers/aix/disks.rb +1 -1
- data/lib/facter/resolvers/aix/partitions.rb +2 -2
- data/lib/facter/resolvers/amzn/os_release_rpm.rb +40 -0
- data/lib/facter/resolvers/freebsd/system_memory.rb +15 -7
- data/lib/facter/resolvers/solaris/dmi_sparc.rb +6 -0
- data/lib/facter/resolvers/solaris/ffi/functions.rb +2 -2
- data/lib/facter/resolvers/solaris/networking.rb +1 -1
- data/lib/facter/resolvers/uname.rb +10 -6
- data/lib/facter/resolvers/xen.rb +3 -1
- data/lib/facter/util/aix/info_extractor.rb +1 -1
- data/lib/facter/util/facts/facts_utils.rb +2 -1
- data/lib/facter/util/resolvers/fingerprint.rb +1 -7
- data/lib/facter/util/resolvers/networking/primary_interface.rb +2 -0
- data/lib/facter/util/resolvers/ssh.rb +1 -9
- data/lib/facter/util/resolvers/ssh_helper.rb +2 -0
- data/lib/facter/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d43b50edf3b5f7815e25fb42dc5cae9f15d2fb2dcbb5fd7146957667f48fa9cb
|
4
|
+
data.tar.gz: 2792c74d6509bddd13f391e9eaac528a5e8a8852fa6f276a433d2bddeb58d3d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bac838d93cb84e7e3934e048368e69106182d25450979749228e2c32a8f2bd21792fd58c245e0cd7b18bbd9e5a11b6bf0edb7067435dd0948b4df25cef1517a8
|
7
|
+
data.tar.gz: b800f80520161b8ec33eb241811227565525c6bff880d271e78906b364c2e7d0fd4f54ac3984d7a6c93084c11de47788a0cd5549af770e91fbc3db9a65415deb
|
@@ -93,7 +93,12 @@ module LegacyFacter
|
|
93
93
|
if data == false
|
94
94
|
log.warn "Could not interpret fact file #{fact.file}"
|
95
95
|
elsif (data == {}) || data.nil?
|
96
|
-
log.debug(
|
96
|
+
log.debug(
|
97
|
+
"Structured data fact file #{fact.file} was parsed but was either empty or an invalid filetype "\
|
98
|
+
'(valid filetypes are .yaml, .json, and .txt).'
|
99
|
+
)
|
100
|
+
elsif !data.is_a?(Hash)
|
101
|
+
log.error("Structured data fact file #{fact.file} was parsed but no key=>value data was returned.")
|
97
102
|
else
|
98
103
|
add_data(data, collection, fact, weight)
|
99
104
|
end
|
@@ -20,10 +20,15 @@ module Facts
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def determine_release_version
|
23
|
+
# For backwards compatibility, use system-release for AL1/AL2
|
23
24
|
version = Facter::Resolvers::ReleaseFromFirstLine.resolve(:release, release_file: '/etc/system-release')
|
25
|
+
if !version.nil? && version != '2'
|
26
|
+
# Use os-release for AL2023 and up
|
27
|
+
version = Facter::Resolvers::Amzn::OsReleaseRpm.resolve(:version)
|
28
|
+
end
|
24
29
|
version ||= Facter::Resolvers::OsRelease.resolve(:version_id)
|
25
30
|
|
26
|
-
Facter::Util::Facts.release_hash_from_string(version)
|
31
|
+
Facter::Util::Facts.release_hash_from_string(version, include_patch: true)
|
27
32
|
end
|
28
33
|
end
|
29
34
|
end
|
@@ -18,7 +18,12 @@ module Facts
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def determine_release_version
|
21
|
+
# For backwards compatibility, use system-release for AL1/AL2
|
21
22
|
version = Facter::Resolvers::ReleaseFromFirstLine.resolve(:release, release_file: '/etc/system-release')
|
23
|
+
if !version.nil? && version != '2'
|
24
|
+
# Use os-release for AL2023 and up
|
25
|
+
version = Facter::Resolvers::Amzn::OsReleaseRpm.resolve(:version)
|
26
|
+
end
|
22
27
|
version ||= Facter::Resolvers::OsRelease.resolve(:version_id)
|
23
28
|
|
24
29
|
Facter::Util::Facts.release_hash_from_string(version)
|
@@ -21,9 +21,10 @@ module Facter
|
|
21
21
|
|
22
22
|
def find_nested_classes(mod, discovered_classes)
|
23
23
|
mod.constants.each do |constant_name|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
obj = mod.const_get(constant_name)
|
25
|
+
if obj.instance_of? Class
|
26
|
+
discovered_classes << obj
|
27
|
+
elsif obj.instance_of? Module
|
27
28
|
find_nested_classes(Module.const_get("#{mod.name}::#{constant_name}"), discovered_classes)
|
28
29
|
end
|
29
30
|
end
|
@@ -244,6 +244,8 @@ os_hierarchy.each do |os|
|
|
244
244
|
require_relative '../../facts/amzn/os/distro/release.rb'
|
245
245
|
require_relative '../../facts/amzn/os/release.rb'
|
246
246
|
|
247
|
+
require_relative '../../resolvers/amzn/os_release_rpm'
|
248
|
+
|
247
249
|
when 'bsd'
|
248
250
|
require_relative '../../facts/bsd/kernelmajversion.rb'
|
249
251
|
require_relative '../../facts/bsd/kernelversion.rb'
|
@@ -18,18 +18,18 @@ module Facter
|
|
18
18
|
# Because a root fact will always be resolved by a collection of child facts,
|
19
19
|
# we can return one or more child facts for each parent.
|
20
20
|
#
|
21
|
-
#
|
22
|
-
#
|
21
|
+
# @param query_list [Array] The list of facts to search for
|
22
|
+
# @param loaded_facts [Array] All of the fact definitions for the current operating system
|
23
23
|
#
|
24
|
-
#
|
25
|
-
def parse(query_list,
|
24
|
+
# @return [Array<SearchedFact>] a list of searchable facts that resolve the user's query
|
25
|
+
def parse(query_list, loaded_facts)
|
26
26
|
matched_facts = []
|
27
27
|
@query_list = query_list
|
28
28
|
|
29
|
-
return no_user_query(
|
29
|
+
return no_user_query(loaded_facts) unless query_list.any?
|
30
30
|
|
31
31
|
query_list.each do |query|
|
32
|
-
found_facts = search_for_facts(query,
|
32
|
+
found_facts = search_for_facts(query, loaded_facts)
|
33
33
|
matched_facts << found_facts
|
34
34
|
end
|
35
35
|
|
@@ -44,15 +44,17 @@ module Facter
|
|
44
44
|
searched_facts
|
45
45
|
end
|
46
46
|
|
47
|
-
def search_for_facts(query,
|
47
|
+
def search_for_facts(query, loaded_facts)
|
48
48
|
resolvable_fact_list = []
|
49
49
|
query = query.to_s
|
50
50
|
query_tokens = query.end_with?('.*') ? [query] : query.split('.')
|
51
51
|
size = query_tokens.size
|
52
52
|
|
53
|
+
# Try to match the most specific query_tokens to the least, returning the first match
|
53
54
|
size.times do |i|
|
54
55
|
query_token_range = 0..size - i - 1
|
55
|
-
|
56
|
+
query_fact = query_tokens[query_token_range].join('.')
|
57
|
+
resolvable_fact_list = get_facts_matching_tokens(query_tokens, query_fact, loaded_facts)
|
56
58
|
|
57
59
|
return resolvable_fact_list if resolvable_fact_list.any?
|
58
60
|
end
|
@@ -62,12 +64,10 @@ module Facter
|
|
62
64
|
resolvable_fact_list
|
63
65
|
end
|
64
66
|
|
65
|
-
def get_facts_matching_tokens(query_tokens,
|
67
|
+
def get_facts_matching_tokens(query_tokens, query_fact, loaded_facts)
|
66
68
|
resolvable_fact_list = []
|
67
69
|
|
68
|
-
|
69
|
-
query_fact = query_tokens[query_token_range].join('.')
|
70
|
-
|
70
|
+
loaded_facts.each do |loaded_fact|
|
71
71
|
next unless found_fact?(loaded_fact.name, query_fact)
|
72
72
|
|
73
73
|
searched_fact = construct_loaded_fact(query_tokens, loaded_fact)
|
@@ -79,16 +79,20 @@ module Facter
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def found_fact?(fact_name, query_fact)
|
82
|
+
# This is the case where the fact_name contains a wildcard like
|
83
|
+
# blockdevice_.*_model and we're querying for the legacy fact
|
84
|
+
# specifically using 'blockdevice_sba_model' and we don't want the query
|
85
|
+
# 'blockdevice.sba.model' to match
|
82
86
|
fact_with_wildcard = fact_name.include?('.*') && !query_fact.include?('.')
|
83
87
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
88
|
+
if fact_with_wildcard
|
89
|
+
# fact_name contains wildcard, so we're intentially not escaping.
|
90
|
+
query_fact.match("^#{fact_name}$")
|
91
|
+
else
|
92
|
+
processed_equery_fact = query_fact.gsub('\\', '\\\\\\\\')
|
93
|
+
# Must escape metacharacters (like dots) to ensure the correct fact is found
|
94
|
+
fact_name.match("^#{Regexp.escape(processed_equery_fact)}($|\\.)")
|
95
|
+
end
|
92
96
|
end
|
93
97
|
|
94
98
|
def construct_loaded_fact(query_tokens, loaded_fact)
|
@@ -47,7 +47,7 @@ module Facter
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def compute_size(size_hash)
|
50
|
-
physical_partitions = size_hash['TOTAL PPs'].to_i
|
50
|
+
physical_partitions = size_hash['TOTAL PPs'].to_i
|
51
51
|
size_physical_partition = size_hash['PP SIZE']
|
52
52
|
exp = if size_physical_partition[/mega/]
|
53
53
|
Facter::Util::Aix::InfoExtractor::MEGABYTES_EXPONENT
|
@@ -56,14 +56,14 @@ module Facter
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def compute_size(info_hash)
|
59
|
-
|
59
|
+
logical_partitions = info_hash['LPs'].to_i
|
60
60
|
size_physical_partition = info_hash['PP SIZE']
|
61
61
|
exp = if size_physical_partition[/mega/]
|
62
62
|
Facter::Util::Aix::InfoExtractor::MEGABYTES_EXPONENT
|
63
63
|
else
|
64
64
|
Facter::Util::Aix::InfoExtractor::GIGABYTES_EXPONENT
|
65
65
|
end
|
66
|
-
size_physical_partition.to_i *
|
66
|
+
size_physical_partition.to_i * logical_partitions * exp
|
67
67
|
end
|
68
68
|
end
|
69
69
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Facter
|
4
|
+
module Resolvers
|
5
|
+
module Amzn
|
6
|
+
class OsReleaseRpm < BaseResolver
|
7
|
+
init_resolver
|
8
|
+
|
9
|
+
class << self
|
10
|
+
private
|
11
|
+
|
12
|
+
def post_resolve(fact_name, _options)
|
13
|
+
@fact_list.fetch(fact_name) { rpm_system_call(fact_name) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def rpm_system_call(fact_name)
|
17
|
+
output = Facter::Core::Execution.execute(
|
18
|
+
'rpm -q --qf \'%{NAME}\n%{VERSION}\n%{RELEASE}\n%{VENDOR}\' -f /etc/os-release',
|
19
|
+
logger: log
|
20
|
+
)
|
21
|
+
build_fact_list(output)
|
22
|
+
|
23
|
+
@fact_list[fact_name]
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_fact_list(output)
|
27
|
+
rpm_results = output.split("\n")
|
28
|
+
|
29
|
+
return if rpm_results.empty?
|
30
|
+
|
31
|
+
@fact_list[:package],
|
32
|
+
@fact_list[:version],
|
33
|
+
@fact_list[:release],
|
34
|
+
@fact_list[:vendor] = rpm_results.map(&:strip)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -15,25 +15,33 @@ module Facter
|
|
15
15
|
|
16
16
|
def calculate_system_memory(fact_name)
|
17
17
|
read_total_memory_in_bytes
|
18
|
-
|
18
|
+
read_used_memory_in_bytes
|
19
19
|
|
20
|
-
@fact_list[:
|
20
|
+
@fact_list[:available_bytes] = @fact_list[:total_bytes] - @fact_list[:used_bytes]
|
21
21
|
@fact_list[:capacity] = Facter::Util::Resolvers::FilesystemHelper
|
22
22
|
.compute_capacity(@fact_list[:used_bytes], @fact_list[:total_bytes])
|
23
23
|
|
24
24
|
@fact_list[fact_name]
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
def pagesize
|
28
|
+
@pagesize ||= Facter::Freebsd::FfiHelper.sysctl_by_name(:long, 'vm.stats.vm.v_page_size')
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_used_memory_in_bytes
|
32
|
+
require_relative 'ffi/ffi_helper'
|
33
|
+
|
34
|
+
@fact_list[:used_bytes] = pagesize * (
|
35
|
+
Facter::Freebsd::FfiHelper.sysctl_by_name(:long, 'vm.stats.vm.v_active_count') +
|
36
|
+
Facter::Freebsd::FfiHelper.sysctl_by_name(:long, 'vm.stats.vm.v_wire_count')
|
37
|
+
)
|
31
38
|
end
|
32
39
|
|
33
40
|
def read_total_memory_in_bytes
|
34
41
|
require_relative 'ffi/ffi_helper'
|
35
42
|
|
36
|
-
@fact_list[:total_bytes] =
|
43
|
+
@fact_list[:total_bytes] = pagesize *
|
44
|
+
Facter::Freebsd::FfiHelper.sysctl_by_name(:long, 'vm.stats.vm.v_page_count')
|
37
45
|
end
|
38
46
|
end
|
39
47
|
end
|
@@ -19,6 +19,12 @@ module Facter
|
|
19
19
|
|
20
20
|
matches = output.match(/System Configuration:\s+(.+?)\s+sun\d+\S+\s+(.+)/)&.captures
|
21
21
|
|
22
|
+
# There are circumstances (e.g. in non-global zones) when prtdiag
|
23
|
+
# will return text, but it's an error message or some other string
|
24
|
+
# that isn't parsed by the above match/capture. In that case, we
|
25
|
+
# simply return.
|
26
|
+
return if matches.nil?
|
27
|
+
|
22
28
|
@fact_list[:manufacturer] = matches[0]&.strip
|
23
29
|
@fact_list[:product_name] = matches[1]&.strip
|
24
30
|
|
@@ -10,7 +10,7 @@ module Facter
|
|
10
10
|
|
11
11
|
attach_function :ioctl_base, :ioctl, %i[int int pointer], :int
|
12
12
|
attach_function :open_socket, :socket, %i[int int int], :int
|
13
|
-
attach_function :close_socket, :
|
13
|
+
attach_function :close_socket, :close, %i[int], :int
|
14
14
|
attach_function :inet_ntop, %i[int pointer pointer uint], :string
|
15
15
|
|
16
16
|
def self.ioctl(call_const, pointer, address_family = AF_INET)
|
@@ -18,7 +18,7 @@ module Facter
|
|
18
18
|
begin
|
19
19
|
ioctl_base(fd, call_const, pointer)
|
20
20
|
ensure
|
21
|
-
Ioctl.close_socket(fd
|
21
|
+
Ioctl.close_socket(fd)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -79,7 +79,7 @@ module Facter
|
|
79
79
|
ioctl = FFI::Ioctl.ioctl(FFI::SIOCGLIFMTU, lifreq, lifreq.ss_family)
|
80
80
|
|
81
81
|
if ioctl == -1
|
82
|
-
@log.error("
|
82
|
+
@log.error("Could not read MTU, error code is: #{::FFI::LastError.error}")
|
83
83
|
return
|
84
84
|
end
|
85
85
|
|
@@ -28,12 +28,16 @@ module Facter
|
|
28
28
|
def build_fact_list(output)
|
29
29
|
uname_results = output.split("\n")
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
31
|
+
if !uname_results.empty?
|
32
|
+
@fact_list[:machine],
|
33
|
+
@fact_list[:nodename],
|
34
|
+
@fact_list[:processor],
|
35
|
+
@fact_list[:kernelrelease],
|
36
|
+
@fact_list[:kernelname],
|
37
|
+
@fact_list[:kernelversion] = uname_results.map(&:strip)
|
38
|
+
else
|
39
|
+
log.warn('Request to uname returned no output. Uname related facts are not populated.')
|
40
|
+
end
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
data/lib/facter/resolvers/xen.rb
CHANGED
@@ -26,7 +26,9 @@ module Facter
|
|
26
26
|
|
27
27
|
def detect_xen_type
|
28
28
|
xen_type = 'xen0' if File.exist?('/dev/xen/evtchn')
|
29
|
-
|
29
|
+
if !xen_type && (File.exist?('/proc/xen') || (File.exist?('/dev/xvda1') && !File.symlink?('/dev/xvda1')))
|
30
|
+
xen_type = 'xenu'
|
31
|
+
end
|
30
32
|
|
31
33
|
xen_type
|
32
34
|
end
|
@@ -61,7 +61,7 @@ module Facter
|
|
61
61
|
properties = PROPERTIES[cmd]
|
62
62
|
properties.each do |property|
|
63
63
|
str = (properties - [property]).join('|')
|
64
|
-
matcher = content.match(
|
64
|
+
matcher = content.match(/(?:^|^[^:]+:[^:]+)#{Regexp.escape(property)}([^\n]*?)(#{str}|\n|$)/s)
|
65
65
|
if matcher
|
66
66
|
value = matcher[1].strip
|
67
67
|
property_hash[property.split(':').first] = value
|
@@ -25,7 +25,7 @@ module Facter
|
|
25
25
|
os
|
26
26
|
end
|
27
27
|
|
28
|
-
def release_hash_from_string(output)
|
28
|
+
def release_hash_from_string(output, include_patch: false)
|
29
29
|
return unless output
|
30
30
|
|
31
31
|
versions = output.split('.')
|
@@ -33,6 +33,7 @@ module Facter
|
|
33
33
|
release['full'] = output
|
34
34
|
release['major'] = versions[0]
|
35
35
|
release['minor'] = versions[1] if versions[1]
|
36
|
+
release['patch'] = versions[2] if versions[2] && include_patch
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
@@ -57,6 +57,8 @@ module Facter
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def find_in_interfaces(interfaces)
|
60
|
+
return if interfaces.nil?
|
61
|
+
|
60
62
|
interfaces.each do |iface_name, interface|
|
61
63
|
interface[:bindings]&.each do |binding|
|
62
64
|
return iface_name unless Facter::Util::Resolvers::Networking.ignored_ip_address(binding[:address])
|
@@ -3,15 +3,7 @@
|
|
3
3
|
module Facter
|
4
4
|
module Util
|
5
5
|
module Resolvers
|
6
|
-
|
7
|
-
attr_accessor :fingerprint, :type, :key, :name
|
8
|
-
def initialize(fingerprint, type, key, name)
|
9
|
-
@fingerprint = fingerprint
|
10
|
-
@type = type
|
11
|
-
@key = key
|
12
|
-
@name = name
|
13
|
-
end
|
14
|
-
end
|
6
|
+
Ssh = Struct.new(:fingerprint, :type, :key, :name)
|
15
7
|
end
|
16
8
|
end
|
17
9
|
end
|
@@ -9,6 +9,7 @@ module Facter
|
|
9
9
|
class SshHelper
|
10
10
|
class << self
|
11
11
|
SSH_NAME = { 'ssh-dss' => 'dsa', 'ecdsa-sha2-nistp256' => 'ecdsa',
|
12
|
+
'ecdsa-sha2-nistp384' => 'ecdsa', 'ecdsa-sha2-nistp521' => 'ecdsa',
|
12
13
|
'ssh-ed25519' => 'ed25519', 'ssh-rsa' => 'rsa' }.freeze
|
13
14
|
SSH_FINGERPRINT = { 'rsa' => 1, 'dsa' => 2, 'ecdsa' => 3, 'ed25519' => 4 }.freeze
|
14
15
|
|
@@ -16,6 +17,7 @@ module Facter
|
|
16
17
|
key_name = SSH_NAME[key_type]
|
17
18
|
return unless key_name
|
18
19
|
|
20
|
+
# decode64 ignores non-base64 characters including newlines
|
19
21
|
decoded_key = Base64.decode64(key)
|
20
22
|
ssh_fp = SSH_FINGERPRINT[key_name]
|
21
23
|
sha1 = "SSHFP #{ssh_fp} 1 #{Digest::SHA1.new.update(decoded_key)}"
|
data/lib/facter/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: facter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.5.
|
4
|
+
version: 4.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -934,6 +934,7 @@ files:
|
|
934
934
|
- lib/facter/resolvers/aix/partitions.rb
|
935
935
|
- lib/facter/resolvers/aix/processors.rb
|
936
936
|
- lib/facter/resolvers/aix/serialnumber.rb
|
937
|
+
- lib/facter/resolvers/amzn/os_release_rpm.rb
|
937
938
|
- lib/facter/resolvers/augeas.rb
|
938
939
|
- lib/facter/resolvers/az.rb
|
939
940
|
- lib/facter/resolvers/base_resolver.rb
|
@@ -1101,7 +1102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
1101
1102
|
- !ruby/object:Gem::Version
|
1102
1103
|
version: '0'
|
1103
1104
|
requirements: []
|
1104
|
-
rubygems_version: 3.4.
|
1105
|
+
rubygems_version: 3.4.20
|
1105
1106
|
signing_key:
|
1106
1107
|
specification_version: 4
|
1107
1108
|
summary: Facter, a system inventory tool
|