Skip to content

Commit 0faa16c

Browse files
committed
(FACT-2786) Fix fact caching if fact is defined in multiple groups
1 parent 202f8d2 commit 0faa16c

6 files changed

Lines changed: 89 additions & 36 deletions

File tree

acceptance/tests/custom_facts/cached_custom_fact.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373

7474
step 'should read from the cached file for a custom fact that has been cached' do
7575
on(agent, facter("#{custom_fact_name} --debug", environment: env)) do |facter_result|
76-
assert_match(/Loading cached custom facts from file ".+"|loading cached values for cached-custom-facts facts/, facter_result.stderr,
76+
assert_match(/Loading cached custom facts from file ".+"|loading cached values for random_custom_fact facts/, facter_result.stderr,
7777
'Expected debug message to state that cached custom facts are read from file')
7878
end
7979
end

acceptance/tests/custom_facts/weighted_cached_custom_facts.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585

8686
step 'should read from the cached file for a custom fact that has been cached' do
8787
on(agent, facter("#{duplicate_custom_fact_name} --debug", environment: env)) do |facter_result|
88-
assert_match(/Loading cached custom facts from file ".+"|loading cached values for cached-custom-facts facts/, facter_result.stderr,
88+
assert_match(/Loading cached custom facts from file ".+"|loading cached values for random_custom_fact facts/, facter_result.stderr,
8989
'Expected debug message to state that cached custom facts are read from file')
9090
end
9191
end

lib/facter/custom_facts/util/directory_loader.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def load_directory_entries(_collection)
5959
basename = File.basename(file)
6060
next if file_blocked?(basename)
6161

62-
if facts.find { |f| f.name == basename } && cm.group_cached?(basename)
62+
if facts.find { |f| f.name == basename } && cm.fact_cache_enabled?(basename)
6363
Facter.log_exception(Exception.new("Caching is enabled for group \"#{basename}\" while "\
6464
'there are at least two external facts files with the same filename'))
6565
else

lib/facter/framework/config/fact_groups.rb

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module Facter
44
class FactGroups
5-
attr_reader :groups, :block_list
5+
attr_reader :groups, :block_list, :facts_ttls
66

77
@groups_ttls = []
88

@@ -14,6 +14,7 @@ def initialize(group_list_path = nil)
1414
@groups ||= File.readable?(@groups_file_path) ? Hocon.load(@groups_file_path) : {}
1515
load_groups
1616
load_groups_from_options
17+
load_facts_ttls
1718
end
1819

1920
# Breakes down blocked groups in blocked facts
@@ -31,6 +32,9 @@ def blocked_facts
3132

3233
# Get the group name a fact is part of
3334
def get_fact_group(fact_name)
35+
fact = get_fact(fact_name)
36+
return fact[:group] if fact
37+
3438
@groups.detect { |k, v| break k if Array(v).find { |f| fact_name =~ /^#{f}.*/ } }
3539
end
3640

@@ -41,6 +45,15 @@ def get_group_ttls(group_name)
4145
ttls_to_seconds(ttls[group_name])
4246
end
4347

48+
def get_fact(fact_name)
49+
return @facts_ttls[fact_name] if @facts_ttls[fact_name]
50+
51+
fact = @facts_ttls.select { |k, v| break v if fact_name =~ /^#{k}\..*/ }
52+
return nil if fact == {}
53+
54+
fact
55+
end
56+
4457
private
4558

4659
def load_groups_from_options
@@ -55,10 +68,30 @@ def load_groups_from_options
5568
end
5669
end
5770

71+
def load_facts_ttls
72+
@facts_ttls ||= {}
73+
return if @groups_ttls == []
74+
75+
@groups_ttls.reduce(:merge).each do |group, ttls|
76+
ttls = ttls_to_seconds(ttls)
77+
if @groups[group]
78+
@groups[group].each do |fact|
79+
if @facts_ttls[fact]
80+
@facts_ttls[fact] = { ttls: ttls, group: group } if @facts_ttls[fact][:ttls] < ttls
81+
else
82+
@facts_ttls[fact] = { ttls: ttls, group: group }
83+
end
84+
end
85+
else
86+
@facts_ttls[group] = { ttls: ttls, group: group }
87+
end
88+
end
89+
end
90+
5891
def load_groups
5992
config = ConfigReader.init(Options[:config])
6093
@block_list = config.block_list || {}
61-
@groups_ttls = config.ttls || {}
94+
@groups_ttls = config.ttls || []
6295
@groups.merge!(config.fact_groups) if config.fact_groups
6396
end
6497

lib/facter/framework/core/cache_manager.rb

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,31 +40,42 @@ def cache_facts(resolved_facts)
4040
end
4141
end
4242

43-
def group_cached?(group_name)
44-
cached = @fact_groups.get_group_ttls(group_name) ? true : false
45-
delete_cache(group_name) unless cached
43+
def fact_cache_enabled?(fact_name)
44+
fact = @fact_groups.get_fact(fact_name)
45+
cached = if fact
46+
!fact[:ttls].nil?
47+
else
48+
false
49+
end
50+
51+
fact_group = @fact_groups.get_fact_group(fact_name)
52+
delete_cache(fact_group) if fact_group && !cached
4653
cached
4754
end
4855

4956
private
5057

5158
def resolve_fact(searched_fact)
52-
group_name = if searched_fact.file
53-
searched_fact.name
54-
else
55-
@fact_groups.get_fact_group(searched_fact.name)
56-
end
59+
return unless fact_cache_enabled?(searched_fact.name)
5760

58-
return unless group_name
61+
fact = if searched_fact.file
62+
@fact_groups.get_fact(File.basename(searched_fact.file))
63+
else
64+
@fact_groups.get_fact(searched_fact.name)
65+
end
5966

60-
return unless group_cached?(group_name)
67+
return unless fact
6168

62-
return unless check_ttls?(group_name)
69+
return unless check_ttls?(fact[:group], fact[:ttls])
6370

64-
data = read_group_json(group_name)
71+
read_fact(searched_fact, fact[:group])
72+
end
73+
74+
def read_fact(searched_fact, fact_group)
75+
data = read_group_json(fact_group)
6576
return unless data
6677

67-
@log.debug("loading cached values for #{group_name} facts")
78+
@log.debug("loading cached values for #{searched_fact.name} facts")
6879

6980
create_facts(searched_fact, data)
7081
end
@@ -93,7 +104,7 @@ def cache_fact(fact)
93104
end
94105
return if !group_name || fact.value.nil?
95106

96-
return unless group_cached?(group_name)
107+
return unless fact_cache_enabled?(fact.name)
97108

98109
@groups[group_name] ||= {}
99110
@groups[group_name][fact.name] = fact.value
@@ -106,10 +117,12 @@ def write_cache
106117
end
107118

108119
@groups.each do |group_name, data|
109-
next unless check_ttls?(group_name)
120+
next unless check_ttls?(group_name, @fact_groups.get_group_ttls(group_name))
110121

111-
@log.debug("caching values for #{group_name} facts")
112122
cache_file_name = File.join(@cache_dir, group_name)
123+
next if File.readable?(cache_file_name)
124+
125+
@log.debug("caching values for #{group_name} facts")
113126
File.write(cache_file_name, JSON.pretty_generate(data))
114127
end
115128
end
@@ -128,8 +141,7 @@ def read_group_json(group_name)
128141
@groups[group_name] = data
129142
end
130143

131-
def check_ttls?(group_name)
132-
ttls = @fact_groups.get_group_ttls(group_name)
144+
def check_ttls?(group_name, ttls)
133145
return false unless ttls
134146

135147
cache_file_name = File.join(@cache_dir, group_name)

spec/facter/cache_manager_spec.rb

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
let(:group_name) { 'operating system' }
2626
let(:cache_file_name) { File.join(cache_dir, group_name) }
2727
let(:fact_groups) { instance_spy(Facter::FactGroups) }
28+
let(:os_fact) { { ttls: 60, group: 'operating system' } }
29+
let(:external_fact) { { ttls: 60, group: 'ext_file.txt' } }
2830

2931
before do
3032
allow(LegacyFacter::Util::Config).to receive(:facts_cache_dir).and_return(cache_dir)
@@ -72,15 +74,18 @@
7274
allow(File).to receive(:directory?).with(cache_dir).and_return(true)
7375
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
7476
allow(fact_groups).to receive(:get_fact_group).with('my_custom_fact').and_return(nil)
77+
allow(fact_groups).to receive(:get_fact_group).with('ext_file.txt').and_return(nil)
7578
allow(fact_groups).to receive(:get_group_ttls).with('ext_file.txt').and_return(nil)
79+
allow(fact_groups).to receive(:get_fact).with('ext_file.txt').and_return(nil)
80+
allow(fact_groups).to receive(:get_fact).with('my_custom_fact').and_return(nil)
7681
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
7782
allow(File).to receive(:mtime).with(cache_file_name).and_return(Time.now)
7883
allow(Facter::Util::FileHelper).to receive(:safe_read).with(cache_file_name).and_return(cached_core_fact)
7984
allow(Facter::Options).to receive(:[]).with(:cache).and_return(true)
8085
end
8186

8287
it 'returns cached fact' do
83-
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000)
88+
allow(fact_groups).to receive(:get_fact).with('os').and_return(os_fact)
8489
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
8590
allow(File).to receive(:readable?).with(File.join(cache_dir, 'ext_file.txt')).and_return(false)
8691

@@ -91,7 +96,7 @@
9196
end
9297

9398
it 'returns searched fact' do
94-
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000)
99+
allow(fact_groups).to receive(:get_fact).with('os').and_return(os_fact)
95100
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
96101
allow(File).to receive(:readable?).with(File.join(cache_dir, 'ext_file.txt')).and_return(false)
97102

@@ -103,18 +108,20 @@
103108
end
104109

105110
it 'deletes cache file' do
106-
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil)
111+
allow(fact_groups).to receive(:get_fact).with('os').and_return(nil)
107112
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
108113
allow(File).to receive(:delete).with(cache_file_name)
114+
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
109115
allow(File).to receive(:readable?).with(File.join(cache_dir, 'ext_file.txt')).and_return(false)
110116

111117
cache_manager.resolve_facts(searched_facts)
112118
expect(File).to have_received(:delete).with(cache_file_name)
113119
end
114120

115121
it 'returns cached external facts' do
116-
allow(fact_groups).to receive(:get_group_ttls).with('ext_file.txt').and_return(1000)
117-
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil)
122+
allow(fact_groups).to receive(:get_fact).with('os').and_return(nil)
123+
allow(fact_groups).to receive(:get_fact).with('my_custom_fact').and_return(nil)
124+
allow(fact_groups).to receive(:get_fact).with('ext_file.txt').and_return(external_fact)
118125
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
119126
allow(Facter::Util::FileHelper).to receive(:safe_read).with(File.join(cache_dir, 'ext_file.txt'))
120127
.and_return(cached_external_fact)
@@ -135,7 +142,7 @@
135142
before do
136143
allow(File).to receive(:directory?).with(cache_dir).and_return(true)
137144
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
138-
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil)
145+
allow(fact_groups).to receive(:get_fact).with('os').and_return(nil)
139146
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
140147
allow(File).to receive(:write).with(cache_file_name, cached_core_fact)
141148
allow(Facter::Options).to receive(:[]).with(:cache).and_return(true)
@@ -150,10 +157,10 @@
150157
context 'with cache group' do
151158
before do
152159
allow(File).to receive(:directory?).with(cache_dir).and_return(true)
160+
allow(fact_groups).to receive(:get_fact).with('os').and_return(os_fact)
153161
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
154162
allow(fact_groups).to receive(:get_fact_group).with('my_custom_fact').and_return(nil)
155163
allow(fact_groups).to receive(:get_fact_group).with('my_external_fact').and_return(nil)
156-
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000)
157164
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
158165
allow(File).to receive(:write).with(cache_file_name, cached_core_fact)
159166
allow(Facter::Options).to receive(:[]).with(:cache).and_return(true)
@@ -166,35 +173,36 @@
166173
end
167174
end
168175

169-
describe '#group_cached?' do
176+
describe '#fact_cache_enabled?' do
170177
context 'with ttls' do
171178
before do
172-
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(1000)
179+
allow(fact_groups).to receive(:get_fact).with('os').and_return(os_fact)
173180
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
174181
end
175182

176183
it 'returns true' do
177-
result = cache_manager.group_cached?(group_name)
184+
result = cache_manager.fact_cache_enabled?('os')
178185
expect(result).to be true
179186
end
180187
end
181188

182189
context 'without ttls' do
183190
before do
184-
allow(fact_groups).to receive(:get_group_ttls).with(group_name).and_return(nil)
191+
allow(fact_groups).to receive(:get_fact).with('os').and_return(nil)
192+
allow(fact_groups).to receive(:get_fact_group).with('os').and_return(group_name)
185193
allow(Facter::Options).to receive(:[]).with(:cache).and_return(true)
186194
allow(File).to receive(:delete).with(cache_file_name)
187195
end
188196

189197
it 'returns false' do
190198
allow(File).to receive(:readable?).with(cache_file_name).and_return(false)
191-
result = cache_manager.group_cached?(group_name)
199+
result = cache_manager.fact_cache_enabled?('os')
192200
expect(result).to be false
193201
end
194202

195203
it 'deletes invalid cache file' do
196204
allow(File).to receive(:readable?).with(cache_file_name).and_return(true)
197-
cache_manager.group_cached?(group_name)
205+
cache_manager.fact_cache_enabled?('os')
198206
expect(File).to have_received(:delete).with(cache_file_name)
199207
end
200208
end

0 commit comments

Comments
 (0)