Skip to content

Commit 2e39081

Browse files
author
Kevin Paulisse
committed
Split absent file into a filter
1 parent be5e316 commit 2e39081

8 files changed

Lines changed: 131 additions & 94 deletions

File tree

lib/octocatalog-diff/catalog-diff/differ.rb

Lines changed: 6 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,14 @@ def catdiff
147147
# Remove resources that have been explicitly ignored
148148
filter_diffs_for_ignored_items(result)
149149

150-
# If a file has ensure => absent, there are certain parameters that don't matter anymore. Filter
151-
# out any such parameters from the result array.
152-
filter_diffs_for_absent_files(result) if @opts[:suppress_absent_file_details]
150+
# Legacy options which are now filters
151+
@opts[:filters] ||= []
152+
if @opts[:suppress_absent_file_details] && !@opts[:filters].include?('absent_file')
153+
@opts[:filters] << 'AbsentFile'
154+
end
153155

154156
# Apply any additional pluggable filters.
155-
OctocatalogDiff::CatalogDiff::Filter.apply_filters(result, @opts[:filters])
157+
OctocatalogDiff::CatalogDiff::Filter.apply_filters(result, @opts[:filters]) if @opts[:filters].any?
156158

157159
# That's it!
158160
@logger.debug "Exiting catdiff; change count: #{result.size}"
@@ -166,43 +168,6 @@ def filter_diffs_for_ignored_items(result)
166168
result.reject! { |item| ignored?(item) }
167169
end
168170

169-
# If a file has ensure => absent, there are certain parameters that don't matter anymore. Filter
170-
# out any such parameters from the result array.
171-
# @param result [Array] Diff result list (modified by this method)
172-
def filter_diffs_for_absent_files(result)
173-
@logger.debug "Entering filter_diffs_for_absent_files with #{result.size} diffs"
174-
175-
# Scan for files in the result that are file resources with ensure => absent.
176-
absent_files = Set.new
177-
result.each do |diff|
178-
next unless diff[0] == '~' || diff[0] == '!'
179-
next unless diff[1] =~ /^File\f([^\f]+)\fparameters\fensure$/
180-
next unless ['absent', 'false', false].include?(diff[3])
181-
absent_files.add Regexp.last_match(1)
182-
end
183-
184-
# If there are any absent files, remove all diffs referencing that file, except for
185-
# the change to 'ensure'.
186-
if absent_files.any?
187-
keep = %w(ensure backup force provider)
188-
result.map! do |diff|
189-
if (diff[0] == '!' || diff[0] == '~') && diff[1] =~ /^File\f(.+)\fparameters\f(.+)$/
190-
if absent_files.include?(Regexp.last_match(1)) && !keep.include?(Regexp.last_match(2))
191-
@logger.debug "Removing file=#{Regexp.last_match(1)} parameter=#{Regexp.last_match(2)} for absent file"
192-
nil
193-
else
194-
diff
195-
end
196-
else
197-
diff
198-
end
199-
end
200-
result.compact!
201-
end
202-
203-
@logger.debug "Exiting filter_diffs_for_absent_files with #{result.size} diffs"
204-
end
205-
206171
# Pre-processing of a catalog.
207172
# - Remove 'before' and 'require' from parameters
208173
# - Sort 'tags' array, or remove the tags array if tags are being ignored

lib/octocatalog-diff/catalog-diff/filter.rb

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require_relative 'filter/absent_file'
12
require_relative 'filter/yaml'
23

34
module OctocatalogDiff
@@ -24,14 +25,19 @@ def self.apply_filters(result, filter_names, options = {})
2425
# @param options [Hash] Additional options (optional) to pass to filtered? method
2526
def self.filter(result, filter_class_name, options = {})
2627
filter_class_name = [name.to_s, filter_class_name].join('::')
27-
clazz = Kernel.const_get(filter_class_name)
28-
result.reject! { |item| clazz.filtered?(item, options) }
28+
obj = Kernel.const_get(filter_class_name).new(result)
29+
result.reject! { |item| obj.filtered?(item, options) }
30+
end
31+
32+
# Inherited: Constructor that does nothing. Some filters require working on the entire
33+
# data set and will override this method to perform some pre-processing for efficiency.
34+
def initialize(_diff_array = [])
2935
end
3036

3137
# Inherited: Construct a default `filtered?` method for the subclass via inheritance.
3238
# Each subclass must implement this method, so the default method errors.
33-
def self.filtered?(_item, _options = {})
34-
raise "No `filtered?` method is implemented in #{name}"
39+
def filtered?(_item, _options = {})
40+
raise "No `filtered?` method is implemented in #{self.class}"
3541
end
3642
end
3743
end
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# frozen_string_literal: true
2+
3+
require 'set'
4+
5+
module OctocatalogDiff
6+
module CatalogDiff
7+
class Filter
8+
# Filter out changes in parameters when the "to" resource has ensure => absent.
9+
class AbsentFile < OctocatalogDiff::CatalogDiff::Filter
10+
# Constructor: Since this filter requires knowledge of the entire array of diffs,
11+
# override the inherited method to store those diffs in an instance variable.
12+
def initialize(diffs)
13+
@diffs = diffs
14+
@results = nil
15+
end
16+
17+
# Public: If a file has ensure => absent, there are certain parameters that don't
18+
# matter anymore. Filter out any such parameters from the result array.
19+
# Return true if the difference is in a resource where `ensure => absent` has been
20+
# declared. Return false if they this is not the case.
21+
#
22+
# @param diff [internal diff format] Difference
23+
# @param _options [Hash] Additional options (there are none for this filter)
24+
# @return [Boolean] true if this difference is a YAML file with identical objects, false otherwise
25+
def filtered?(diff, _options = {})
26+
build_results if @results.nil?
27+
@results.include?(diff)
28+
end
29+
30+
private
31+
32+
# Private: The first time `.filtered?` is called, build up the cache of results.
33+
# Returns nothing, but populates @results.
34+
def build_results
35+
# Which files can we ignore?
36+
@files_to_ignore = Set.new
37+
@diffs.each do |diff|
38+
next unless diff[0] == '~' || diff[0] == '!'
39+
next unless diff[1] =~ /^File\f([^\f]+)\fparameters\fensure$/
40+
next unless ['absent', 'false', false].include?(diff[3])
41+
@files_to_ignore.add Regexp.last_match(1)
42+
end
43+
44+
# Based on that, which diffs can we ignore?
45+
@results = Set.new @diffs.reject { |diff| keep_diff?(diff) }
46+
end
47+
48+
# Private: Determine whether to keep a particular diff.
49+
# @param diff [OctocatalogDiff::API::V1::Diff] Difference under consideration
50+
# @return [Boolean] true = keep, false = discard
51+
def keep_diff?(diff)
52+
keep = %w(ensure backup force provider)
53+
if (diff[0] == '!' || diff[0] == '~') && diff[1] =~ /^File\f(.+)\fparameters\f(.+)$/
54+
if @files_to_ignore.include?(Regexp.last_match(1)) && !keep.include?(Regexp.last_match(2))
55+
return false
56+
end
57+
end
58+
true
59+
end
60+
end
61+
end
62+
end
63+
end

lib/octocatalog-diff/catalog-diff/filter/yaml.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class YAML < OctocatalogDiff::CatalogDiff::Filter
1414
# @param diff [Array] Difference
1515
# @param _options [Hash] Additional options (there are none for this filter)
1616
# @return [Boolean] true if this difference is a YAML file with identical objects, false otherwise
17-
def self.filtered?(diff, _options = {})
17+
def filtered?(diff, _options = {})
1818
# Skip additions or removals - focus only on changes
1919
return false unless diff[0] == '~' || diff[0] == '!'
2020

spec/octocatalog-diff/tests/catalog-diff/differ_spec.rb

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1251,35 +1251,4 @@
12511251
expect(result[0]).to eq(['!', "Class\fOpenssl::Package\fparameters\fcommon-array", [1, 2, 3], [1, 5, 25], fileref, fileref])
12521252
end
12531253
end
1254-
1255-
describe '#filter_diffs_for_absent_files' do
1256-
before(:each) do
1257-
empty_puppet_catalog_json = File.read(OctocatalogDiff::Spec.fixture_path('catalogs/catalog-empty.json'))
1258-
empty_puppet_catalog = OctocatalogDiff::Catalog.new(json: empty_puppet_catalog_json)
1259-
logger, @logger_str = OctocatalogDiff::Spec.setup_logger
1260-
@obj = OctocatalogDiff::CatalogDiff::Differ.new({ logger: logger }, empty_puppet_catalog, empty_puppet_catalog)
1261-
@orig = [
1262-
['~', "File\f/tmp/foo\fparameters\fensure", 'file', 'absent'],
1263-
['~', "File\f/tmp/foo\fparameters\fowner", 'root', 'nobody'],
1264-
['~', "File\f/tmp/foo\fparameters\fbackup", true, nil],
1265-
['~', "File\f/tmp/foo\fparameters\fforce", false, nil],
1266-
['~', "File\f/tmp/foo\fparameters\fprovider", 'root', nil],
1267-
['~', "File\f/tmp/bar\fparameters\fensure", 'file', 'link'],
1268-
['~', "File\f/tmp/bar\fparameters\ftarget", nil, '/tmp/foo'],
1269-
['~', "Exec\f/tmp/bar\fparameters\fcommand", nil, '/tmp/foo']
1270-
]
1271-
@result = @orig.dup
1272-
@obj.send(:filter_diffs_for_absent_files, @result)
1273-
end
1274-
1275-
it 'should filter out some attributes for ensure=>absent file' do
1276-
expect(@result).to eq(@orig.values_at(0, 2, 3, 4, 5, 6, 7))
1277-
end
1278-
1279-
it 'should log messages' do
1280-
expect(@logger_str.string).to match(/Entering filter_diffs_for_absent_files with 8 diffs/)
1281-
expect(@logger_str.string).to match(%r{Removing file=/tmp/foo parameter=owner for absent file})
1282-
expect(@logger_str.string).to match(/Exiting filter_diffs_for_absent_files with 7 diffs/)
1283-
end
1284-
end
12851254
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../spec_helper'
4+
require OctocatalogDiff::Spec.require_path('/catalog')
5+
require OctocatalogDiff::Spec.require_path('/catalog-diff/filter/absent_file')
6+
7+
describe OctocatalogDiff::CatalogDiff::Filter::AbsentFile do
8+
it 'should filter out some attributes for ensure=>absent file' do
9+
orig = [
10+
['~', "File\f/tmp/foo\fparameters\fensure", 'file', 'absent'],
11+
['~', "File\f/tmp/foo\fparameters\fowner", 'root', 'nobody'],
12+
['~', "File\f/tmp/foo\fparameters\fbackup", true, nil],
13+
['~', "File\f/tmp/foo\fparameters\fforce", false, nil],
14+
['~', "File\f/tmp/foo\fparameters\fprovider", 'root', nil],
15+
['~', "File\f/tmp/bar\fparameters\fensure", 'file', 'link'],
16+
['~', "File\f/tmp/bar\fparameters\ftarget", nil, '/tmp/foo'],
17+
['~', "Exec\f/tmp/bar\fparameters\fcommand", nil, '/tmp/foo']
18+
]
19+
testobj = described_class.new(orig)
20+
result = orig.reject { |x| testobj.filtered?(x) }
21+
expect(result).to eq(orig.values_at(0, 2, 3, 4, 5, 6, 7))
22+
end
23+
end

spec/octocatalog-diff/tests/catalog-diff/filter/yaml_spec.rb

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,70 +4,72 @@
44
require OctocatalogDiff::Spec.require_path('/catalog-diff/filter/yaml')
55

66
describe OctocatalogDiff::CatalogDiff::Filter::YAML do
7+
let(:subject) { described_class.new }
8+
79
describe '#filtered?' do
810
let(:str1a) { "---\n foo: bar" }
911
let(:str1b) { "---\nfoo: bar" }
1012
let(:str2) { "---\n foo: baz" }
1113

1214
it 'should not filter out an added resource' do
1315
diff = ['+', "File\ffoobar.yaml", { 'parameters' => { 'content' => str1a } }]
14-
result = described_class.filtered?(diff)
16+
result = subject.filtered?(diff)
1517
expect(result).to eq(false)
1618
end
1719

1820
it 'should not filter out a removed resource' do
1921
diff = ['-', "File\ffoobar.yaml", { 'parameters' => { 'content' => str1a } }]
20-
result = described_class.filtered?(diff)
22+
result = subject.filtered?(diff)
2123
expect(result).to eq(false)
2224
end
2325

2426
it 'should not filter out a non-file resource' do
2527
diff = ['~', "Exec\ffoobar.yaml\fparameters\fcontent", str1a, str1b]
26-
result = described_class.filtered?(diff)
28+
result = subject.filtered?(diff)
2729
expect(result).to eq(false)
2830
end
2931

3032
it 'should not filter out a file whose extension is not .yaml / .yml' do
3133
diff = ['~', "File\ffoobar.json\fparameters\fcontent", str1a, str1b]
32-
result = described_class.filtered?(diff)
34+
result = subject.filtered?(diff)
3335
expect(result).to eq(false)
3436
end
3537

3638
it 'should not filter out a change with no content change' do
3739
diff = ['~', "File\ffoobar.json\fparameters\fowner", 'root', 'nobody']
38-
result = described_class.filtered?(diff)
40+
result = subject.filtered?(diff)
3941
expect(result).to eq(false)
4042
end
4143

4244
it 'should not filter out a change where YAML objects are dissimilar' do
4345
diff = ['~', "File\ffoobar.yaml\fparameters\fcontent", str1a, str2]
44-
result = described_class.filtered?(diff)
46+
result = subject.filtered?(diff)
4547
expect(result).to eq(false)
4648
end
4749

4850
it 'should not filter out a change where YAML is invalid' do
4951
x_str = '---{ "blah": "foo" }'
5052
diff = ['~', "File\ffoobar.yaml\fparameters\fcontent", x_str, x_str]
51-
result = described_class.filtered?(diff)
53+
result = subject.filtered?(diff)
5254
expect(result).to eq(false)
5355
end
5456

5557
it 'should not filter out a change where YAML is unparseable' do
5658
x_str = "--- !ruby/object:This::Does::Not::Exist\n foo: bar"
5759
diff = ['~', "File\ffoobar.yaml\fparameters\fcontent", x_str, x_str]
58-
result = described_class.filtered?(diff)
60+
result = subject.filtered?(diff)
5961
expect(result).to eq(false)
6062
end
6163

6264
it 'should filter out a whitespace-only change to a .yaml file' do
6365
diff = ['~', "File\ffoobar.yaml\fparameters\fcontent", str1a, str1b]
64-
result = described_class.filtered?(diff)
66+
result = subject.filtered?(diff)
6567
expect(result).to eq(true)
6668
end
6769

6870
it 'should filter out a whitespace-only change to a .yml file' do
6971
diff = ['~', "File\ffoobar.yml\fparameters\fcontent", str1a, str1b]
70-
result = described_class.filtered?(diff)
72+
result = subject.filtered?(diff)
7173
expect(result).to eq(true)
7274
end
7375
end

spec/octocatalog-diff/tests/catalog-diff/filter_spec.rb

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,18 @@
33
require_relative '../spec_helper'
44
require OctocatalogDiff::Spec.require_path('/catalog-diff/filter')
55

6+
# rubocop:disable Style/ClassAndModuleChildren
7+
class OctocatalogDiff::CatalogDiff::Filter::Fake1 < OctocatalogDiff::CatalogDiff::Filter
8+
end
9+
10+
class OctocatalogDiff::CatalogDiff::Filter::Fake2 < OctocatalogDiff::CatalogDiff::Filter
11+
end
12+
# rubocop:enable Style/ClassAndModuleChildren
13+
614
describe OctocatalogDiff::CatalogDiff::Filter do
715
before(:each) do
8-
@class_1 = double
9-
@class_2 = double
16+
@class_1 = OctocatalogDiff::CatalogDiff::Filter::Fake1
17+
@class_2 = OctocatalogDiff::CatalogDiff::Filter::Fake2
1018
allow(Kernel).to receive(:const_get).with('OctocatalogDiff::CatalogDiff::Filter::Fake1').and_return(@class_1)
1119
allow(Kernel).to receive(:const_get).with('OctocatalogDiff::CatalogDiff::Filter::Fake2').and_return(@class_2)
1220
end
@@ -16,8 +24,8 @@
1624
result = [false]
1725
options = { 'Fake1' => { foo: 'bar' } }
1826
classes = %w(Fake1 Fake2)
19-
expect(@class_1).to receive(:'filtered?').with(false, foo: 'bar').and_return(false)
20-
expect(@class_2).to receive(:'filtered?').with(false, {}).and_return(false)
27+
expect_any_instance_of(@class_1).to receive(:'filtered?').with(false, foo: 'bar').and_return(false)
28+
expect_any_instance_of(@class_2).to receive(:'filtered?').with(false, {}).and_return(false)
2129
expect { described_class.apply_filters(result, classes, options) }.not_to raise_error
2230
expect(result).to eq([false])
2331
end
@@ -26,16 +34,17 @@
2634
describe '#filter' do
2735
it 'should call .filtered?() in a class and remove matching items' do
2836
result = [false, true]
29-
expect(@class_1).to receive(:'filtered?').with(false, {}).and_return(false)
30-
expect(@class_1).to receive(:'filtered?').with(true, {}).and_return(true)
37+
expect_any_instance_of(@class_1).to receive(:'filtered?').with(false, {}).and_return(false)
38+
expect_any_instance_of(@class_1).to receive(:'filtered?').with(true, {}).and_return(true)
3139
expect { described_class.filter(result, 'Fake1') }.not_to raise_error
3240
expect(result).to eq([false])
3341
end
3442
end
3543

3644
describe '#filtered?' do
3745
it 'should raise error' do
38-
expect { described_class.filtered?([]) }.to raise_error(RuntimeError, /No `filtered\?` method is implemented/)
46+
testobj = described_class.new
47+
expect { testobj.filtered?([]) }.to raise_error(RuntimeError, /No `filtered\?` method is implemented/)
3948
end
4049
end
4150
end

0 commit comments

Comments
 (0)