-
-
Notifications
You must be signed in to change notification settings - Fork 77
/
Copy pathfacterdb.rb
153 lines (133 loc) · 5.78 KB
/
facterdb.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
require 'facter'
require 'jgrep'
module FacterDB
module Errors
class InvalidFilter < RuntimeError; end
end
# @return [String] returns a giant incomprehensible string of concatenated json data
def self.database
@database ||= "[#{facterdb_fact_files.map { |f| read_json_file(f) }.join(',')}]\n"
end
# @note Call this method at the end of test suite, for example via after(:suite), to reclaim back the memory required to hold json data and filter cache
def self.cleanup
@database = nil
Thread.current[:facterdb_last_filter_seen] = nil
Thread.current[:facterdb_last_facts_seen] = nil
end
# @return [Boolean] returns true if we should use the default facterdb database, false otherwise
# @note If the user passes anything to the FACTERDB_SKIP_DEFAULTDB environment variable we assume
# they want to skip the default db
def self.use_defaultdb?
ENV['FACTERDB_SKIP_DEFAULTDB'].nil?
end
# @return [Boolean] returns true if we should inject the source file name and file path into the json factsets.
# The default is false.
def self.inject_source?
!ENV['FACTERDB_INJECT_SOURCE'].nil?
end
def self.read_json_file(f)
content = File.read(f)
return content unless inject_source?
# Find the opening brace
first_brace = content.index('{')
return content if first_brace.nil?
# Inject source file information
json_injection = "\"_facterdb_filename\": #{File.basename(f).to_json}, "
json_injection += "\"_facterdb_path\": #{File.expand_path(f).to_json}, "
content.insert(first_brace + 1, json_injection)
end
private_class_method :read_json_file
# @return [Array[String]] list of all files found in the default facterdb facts path
def self.default_fact_files
return [] unless use_defaultdb?
proj_root = File.join(File.dirname(File.dirname(__FILE__)))
facts_dir = File.expand_path(File.join(proj_root, 'facts'))
Dir.glob(File.join(facts_dir, '**', '*.facts'))
end
# @return [Array[String]] list of all files found in the user supplied facterdb facts path
# @param fact_paths [String] a comma separated list of paths to search for fact files
def self.external_fact_files(fact_paths = ENV.fetch('FACTERDB_SEARCH_PATHS', nil))
fact_paths ||= ''
return [] if fact_paths.empty?
paths = fact_paths.split(File::PATH_SEPARATOR).map do |fact_path|
unless File.directory?(fact_path)
warn("[FACTERDB] Ignoring external facts path #{fact_path} as it is not a directory")
next nil
end
fact_path = fact_path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
File.join(fact_path.strip, '**', '*.facts')
end.compact
Dir.glob(paths)
end
# @return [Array[String]] list of all files found in the default facterdb facts path and user supplied path
# @note external fact files supplied by the user will take precedence over default fact files found in this gem
def self.facterdb_fact_files
(external_fact_files + default_fact_files).uniq
end
# @deprecated Use {.get_facts} instead.
def self.get_os_facts(facter_version = '*', filter = [])
if facter_version == '*'
if filter.is_a?(Array)
filter_str = filter.map { |f| f.map { |k, v| "#{k}=#{v}" }.join(' and ') }.join(' or ')
elsif filter.is_a?(Hash)
filter_str = filter.map { |k, v| "#{k}=#{v}" }.join(' and ')
elsif filter.is_a?(String)
filter_str = filter
else
raise 'filter must be either an Array a Hash or a String'
end
elsif filter.is_a?(Array)
filter_str = "facterversion=/^#{facter_version}/ and (#{filter.map do |f|
f.map do |k, v|
"#{k}=#{v}"
end.join(' and ')
end.join(' or ')})"
elsif filter.is_a?(Hash)
filter_str = "facterversion=/^#{facter_version}/ and (#{filter.map { |k, v| "#{k}=#{v}" }.join(' and ')})"
elsif filter.is_a?(String)
filter_str = "facterversion=/^#{facter_version}/ and (#{filter})"
else
raise 'filter must be either an Array a Hash or a String'
end
warn "[DEPRECATION] `get_os_facts` is deprecated. Please use `get_facts(#{filter_str})` instead."
get_facts(filter_str)
end
# @return [String] the string filter
# @param filter [Object] The filter to convert to jgrep string
def self.generate_filter_str(filter = nil)
case filter
when ::Array
'(' + filter.map { |f| f.map { |k, v| "#{k}=#{v}" }.join(' and ') }.join(') or (') + ')'
when ::Hash
filter.map { |k, v| "#{k}=#{v}" }.join(' and ')
when ::String
filter
when ::NilClass
''
else
raise Errors::InvalidFilter, "filter must be either an Array a Hash, String, or nil, received #{filter.class}"
end
end
# @return [Boolean] true if the filter is valid against the jgrep filter validator
# @param filters [Object] The filter to convert to jgrep string
def self.valid_filters?(filters)
filter_str = generate_filter_str(filters)
JGrep.validate_filters(filter_str).nil?
rescue RuntimeError
false
end
# @return [Array[Hash[Symbol, Any]]] array of hashes of facts
# @param filter [Object] The filter to convert to jgrep string
def self.get_facts(filter = nil, cache = true)
if cache && filter && filter == Thread.current[:facterdb_last_filter_seen]
return Thread.current[:facterdb_last_facts_seen]
end
filter_str = generate_filter_str(filter)
result = JGrep.jgrep(database, filter_str).map { |hash| hash.map { |k, v| [k.to_sym, v] }.to_h }
if cache
Thread.current[:facterdb_last_filter_seen] = filter
Thread.current[:facterdb_last_facts_seen] = result
end
result
end
end