abide_dev_utils 0.9.7 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +7 -1
  4. data/Gemfile.lock +82 -64
  5. data/Rakefile +28 -0
  6. data/abide_dev_utils.gemspec +3 -1
  7. data/lib/abide_dev_utils/cem/benchmark.rb +291 -0
  8. data/lib/abide_dev_utils/cem/coverage_report.rb +348 -0
  9. data/lib/abide_dev_utils/cem/generate/reference.rb +116 -0
  10. data/lib/abide_dev_utils/cem/generate.rb +10 -0
  11. data/lib/abide_dev_utils/cem/mapping/mapper.rb +155 -0
  12. data/lib/abide_dev_utils/cem.rb +74 -0
  13. data/lib/abide_dev_utils/cli/cem.rb +153 -0
  14. data/lib/abide_dev_utils/cli/jira.rb +1 -1
  15. data/lib/abide_dev_utils/cli/xccdf.rb +15 -1
  16. data/lib/abide_dev_utils/cli.rb +2 -0
  17. data/lib/abide_dev_utils/errors/cem.rb +22 -0
  18. data/lib/abide_dev_utils/errors/general.rb +8 -2
  19. data/lib/abide_dev_utils/errors/ppt.rb +4 -0
  20. data/lib/abide_dev_utils/errors.rb +6 -0
  21. data/lib/abide_dev_utils/files.rb +34 -0
  22. data/lib/abide_dev_utils/markdown.rb +104 -0
  23. data/lib/abide_dev_utils/ppt/facter_utils.rb +140 -0
  24. data/lib/abide_dev_utils/ppt/hiera.rb +297 -0
  25. data/lib/abide_dev_utils/ppt/puppet_module.rb +74 -0
  26. data/lib/abide_dev_utils/ppt.rb +3 -5
  27. data/lib/abide_dev_utils/validate.rb +14 -0
  28. data/lib/abide_dev_utils/version.rb +1 -1
  29. data/lib/abide_dev_utils/xccdf/diff/benchmark/number_title.rb +270 -0
  30. data/lib/abide_dev_utils/xccdf/diff/benchmark/profile.rb +104 -0
  31. data/lib/abide_dev_utils/xccdf/diff/benchmark/property.rb +127 -0
  32. data/lib/abide_dev_utils/xccdf/diff/benchmark/property_existence.rb +47 -0
  33. data/lib/abide_dev_utils/xccdf/diff/benchmark.rb +267 -0
  34. data/lib/abide_dev_utils/xccdf/diff/utils.rb +30 -0
  35. data/lib/abide_dev_utils/xccdf/diff.rb +233 -0
  36. data/lib/abide_dev_utils/xccdf/parser/objects/digest_object.rb +118 -0
  37. data/lib/abide_dev_utils/xccdf/parser/objects/numbered_object.rb +104 -0
  38. data/lib/abide_dev_utils/xccdf/parser/objects.rb +741 -0
  39. data/lib/abide_dev_utils/xccdf/parser.rb +52 -0
  40. data/lib/abide_dev_utils/xccdf.rb +14 -124
  41. data/new_diff.rb +48 -0
  42. metadata +60 -9
  43. data/lib/abide_dev_utils/ppt/coverage.rb +0 -86
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbideDevUtils
4
- VERSION = "0.9.7"
4
+ VERSION = "0.11.0"
5
5
  end
@@ -0,0 +1,270 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/xccdf/diff/benchmark/property_existence'
4
+
5
+ module AbideDevUtils
6
+ module XCCDF
7
+ module Diff
8
+ # Diffs two XCCDF benchmarks using the title / number of the items as the primary
9
+ # diff properties.
10
+ class NumberTitleDiff
11
+ SKIP_DIFF_TYPES = %i[equal both].freeze
12
+
13
+ def initialize(numbered_children, other_numbered_children)
14
+ new_number_title_objs(numbered_children, other_numbered_children)
15
+ end
16
+
17
+ def diff
18
+ @diff ||= find_diffs(@number_title_objs, @other_number_title_objs)
19
+ end
20
+
21
+ def to_s
22
+ parts = []
23
+ @diff.each do |_, diffs|
24
+ diffs.each do |dh|
25
+ parts << dh[:diff_text]
26
+ end
27
+ end
28
+ parts.join("\n")
29
+ end
30
+
31
+ private
32
+
33
+ attr_writer :diff
34
+
35
+ def added_number_title_objs
36
+ added_titles = @self_prop_checker.added_titles
37
+ @other_number_title_objs.select do |nto|
38
+ added_titles.include?(nto.title)
39
+ end
40
+ end
41
+
42
+ def removed_number_title_objs
43
+ removed_titles = @self_prop_checker.removed_titles
44
+ @number_title_objs.select do |nto|
45
+ removed_titles.include?(nto.title)
46
+ end
47
+ end
48
+
49
+ def find_diffs(objs, other_objs)
50
+ diffs = []
51
+ added_number_title_objs.each do |nto|
52
+ change_type = %i[both added]
53
+ stand_in = NumberTitleContainerStandIn.new(change_type)
54
+ diffs << process_diffs([diff_hash(change_type, stand_in, nto)])
55
+ end
56
+ removed_number_title_objs.each do |nto|
57
+ change_type = %i[both removed]
58
+ stand_in = NumberTitleContainerStandIn.new(change_type)
59
+ diffs << process_diffs([diff_hash(change_type, nto, stand_in)])
60
+ end
61
+ objs.each do |obj|
62
+ obj_diffs = other_objs.each_with_object([]) do |other_obj, o_ary|
63
+ obj_diff = obj.diff(other_obj)
64
+ next if SKIP_DIFF_TYPES.include?(obj_diff[0])
65
+
66
+ o_ary << diff_hash(obj_diff, obj, other_obj)
67
+ end
68
+
69
+ processed_obj_diffs = process_diffs(obj_diffs)
70
+ diffs << processed_obj_diffs unless obj_diffs.empty?
71
+ end
72
+ diffs
73
+ end
74
+
75
+ def process_diffs(diffs)
76
+ return {} if diffs.empty?
77
+
78
+ raise "Unexpected diffs: #{diffs}" if diffs.length > 2
79
+
80
+ return diffs[0] if diffs.length == 1
81
+
82
+ if diffs[0][:type][0] == PropChecker.inverse_existence_state[diffs[1][:type][0]]
83
+ diffs[0]
84
+ else
85
+ diffs[1]
86
+ end
87
+ end
88
+
89
+ def diff_hash(diff_type, obj, other_obj)
90
+ {
91
+ self: obj.child,
92
+ other: other_obj.child,
93
+ type: diff_type,
94
+ text: diff_type_text(diff_type, obj, other_obj),
95
+ number: obj.number,
96
+ other_number: other_obj.number,
97
+ title: obj.title,
98
+ other_title: other_obj.title,
99
+ }
100
+ end
101
+
102
+ def diff_type_text(diff_type, obj, other_obj)
103
+ DiffTypeText.text(diff_type, obj, other_obj)
104
+ end
105
+
106
+ def new_number_title_objs(children, other_children)
107
+ number_title_objs = children.map { |c| NumberTitleContainer.new(c) }.sort
108
+ other_number_title_objs = other_children.map { |c| NumberTitleContainer.new(c) }.sort
109
+ @self_prop_checker = PropChecker.new(number_title_objs, other_number_title_objs)
110
+ @other_prop_checker = PropChecker.new(other_number_title_objs, number_title_objs)
111
+ number_title_objs.map { |n| n.prop_checker = @self_prop_checker }
112
+ other_number_title_objs.map { |n| n.prop_checker = @other_prop_checker }
113
+ @number_title_objs = number_title_objs
114
+ @other_number_title_objs = other_number_title_objs
115
+ end
116
+ end
117
+
118
+ # Creates string representations of diff types
119
+ class DiffTypeText
120
+ def self.text(diff_type, obj, other_obj)
121
+ case diff_type[0]
122
+ when :equal
123
+ 'The objects are equal'
124
+ when :title
125
+ "Title changed: Number \"#{obj.number}\": #{obj.title} -> #{other_obj.title}"
126
+ when :number
127
+ number_diff_type_text(diff_type, obj, other_obj)
128
+ when :both
129
+ both_diff_type_text(diff_type, obj, other_obj)
130
+ when :add
131
+ "Add object with number \"#{other_obj.number}\" and title \"#{other_obj.title}\""
132
+ when :remove
133
+ "Remove object with number \"#{obj.number}\" and title \"#{obj.title}\""
134
+ else
135
+ raise ArgumentError, "Unknown diff type: #{diff_type}"
136
+ end
137
+ end
138
+
139
+ def self.number_diff_type_text(diff_type, obj, other_obj)
140
+ case diff_type[1]
141
+ when :added
142
+ "Number changed (New Number): Title \"#{obj.title}\": #{obj.number} -> #{other_obj.number}"
143
+ when :exists
144
+ "Number changed (Existing Number): Title \"#{obj.title}\": #{obj.number} -> #{other_obj.number}"
145
+ else
146
+ raise ArgumentError, "Unknown diff type for number change: #{diff_type[1]}"
147
+ end
148
+ end
149
+
150
+ def self.both_diff_type_text(diff_type, obj, other_obj)
151
+ case diff_type[1]
152
+ when :added
153
+ "Added object: Title \"#{other_obj.title}\": Number \"#{other_obj.number}\""
154
+ when :removed
155
+ "Removed object: Title \"#{obj.title}\": Number \"#{obj.number}\""
156
+ else
157
+ raise ArgumentError, "Unknown diff type for both change: #{diff_type[1]}"
158
+ end
159
+ end
160
+ end
161
+
162
+ # Checks properties for existence in both benchmarks.
163
+ class PropChecker < AbideDevUtils::XCCDF::Diff::PropertyExistenceChecker
164
+ attr_reader :all_numbers, :all_titles, :other_all_numbers, :other_all_titles
165
+
166
+ def initialize(number_title_objs, other_number_title_objs)
167
+ super
168
+ @all_numbers = number_title_objs.map(&:number)
169
+ @all_titles = number_title_objs.map(&:title)
170
+ @other_all_numbers = other_number_title_objs.map(&:number)
171
+ @other_all_titles = other_number_title_objs.map(&:title)
172
+ end
173
+
174
+ def title(title)
175
+ property_existence(title, @all_titles, @other_all_titles)
176
+ end
177
+
178
+ def number(number)
179
+ property_existence(number, @all_numbers, @other_all_numbers)
180
+ end
181
+
182
+ def added_numbers
183
+ added(@all_numbers, @other_all_numbers)
184
+ end
185
+
186
+ def removed_numbers
187
+ removed(@all_numbers, @other_all_numbers)
188
+ end
189
+
190
+ def added_titles
191
+ added(@all_titles, @other_all_titles)
192
+ end
193
+
194
+ def removed_titles
195
+ removed(@all_titles, @other_all_titles)
196
+ end
197
+ end
198
+
199
+ class NumberTitleDiffError < StandardError; end
200
+ class InconsistentDiffTypeError < StandardError; end
201
+
202
+ # Holds a number and title for a child of a benchmark
203
+ # and provides methods to compare it to another child.
204
+ class NumberTitleContainer
205
+ include ::Comparable
206
+ attr_accessor :prop_checker
207
+ attr_reader :child, :number, :title
208
+
209
+ def initialize(child, prop_checker = nil)
210
+ @child = child
211
+ @number = child.number.to_s
212
+ @title = child.title.to_s
213
+ @prop_checker = prop_checker
214
+ end
215
+
216
+ def diff(other)
217
+ return %i[equal exist] if number == other.number && title == other.title
218
+
219
+ if number == other.number && title != other.title
220
+ c_diff = correlate_prop_diff_types(@prop_checker.title(other.title),
221
+ other.prop_checker.title(other.title))
222
+ [:title, c_diff]
223
+ elsif title == other.title && number != other.number
224
+ c_diff = correlate_prop_diff_types(@prop_checker.number(other.number),
225
+ other.prop_checker.number(other.number))
226
+ [:number, c_diff]
227
+ else
228
+ %i[both exist]
229
+ end
230
+ rescue StandardError => e
231
+ err_str = [
232
+ 'Error diffing number and title',
233
+ "Number: #{number}",
234
+ "Title: #{title}",
235
+ "Other number: #{other.number}",
236
+ "Other title: #{other.title}",
237
+ e.message,
238
+ ]
239
+ raise NumberTitleDiffError, err_str.join(', ')
240
+ end
241
+
242
+ def <=>(other)
243
+ child <=> other.child
244
+ end
245
+
246
+ private
247
+
248
+ def correlate_prop_diff_types(self_type, other_type)
249
+ inverse_diff_type = PropChecker.inverse_existence_state[self_type]
250
+ return other_type if inverse_diff_type.nil?
251
+
252
+ self_type
253
+ end
254
+ end
255
+
256
+ # Stand-in object for a NumberTitleContainer when the NumberTitleContainer
257
+ # would not exist. This is used when a change is an add or remove.
258
+ class NumberTitleContainerStandIn
259
+ attr_reader :child, :number, :title
260
+
261
+ def initialize(change_type)
262
+ @change_type = change_type
263
+ @child = nil
264
+ @number = ''
265
+ @title = ''
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/xccdf/diff/benchmark/property_existence'
4
+
5
+ module AbideDevUtils
6
+ module XCCDF
7
+ module Diff
8
+ # Diffs two sets of XCCDF profiles.
9
+ class ProfileDiff
10
+ def initialize(profiles, other_profiles)
11
+ new_profile_rule_objs(profiles, other_profiles)
12
+ end
13
+
14
+ def diff_hash(diff_type, profile1, prof1_rules, profile2, prof2_rules)
15
+ {
16
+
17
+ }
18
+ end
19
+
20
+ private
21
+
22
+ def new_profile_rule_objs(profiles, other_profiles)
23
+ profile_objs = containers_from_profile_list(profiles)
24
+ other_profile_objs = containers_from_profile_list(other_profiles)
25
+ @self_prop_checker = PropertyExistenceChecker.new(profile_objs, other_profile_objs)
26
+ @other_prop_checker = PropertyExistenceChecker.new(other_profile_objs, profile_objs)
27
+ profile_objs.map { |p| p.prop_checker = @self_prop_checker }
28
+ other_profile_objs.map { |p| p.prop_checker = @other_prop_checker }
29
+ @profile_rule_objs = profile_objs
30
+ @other_profile_rule_objs = other_profile_objs
31
+ end
32
+
33
+ def containers_from_profile_list(profile_list)
34
+ profile_list.each_with_object([]) do |profile, ary|
35
+ ary << ProfileRuleContainer.new(profile)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Checks property existence in both profiles.
41
+ class PropChecker < AbideDevUtils::XCCDF::Diff::Benchmark::PropertyExistence
42
+ def initialize(profile_rule_objs, other_profile_rule_objs)
43
+ super
44
+ @profile_rule_objs = profile_rule_objs
45
+ @other_profile_rule_objs = other_profile_rule_objs
46
+ @profiles = profile_rule_objs.map(&:profile)
47
+ @other_profiles = other_profile_rule_objs.map(&:profile)
48
+ end
49
+
50
+ def profile(profile)
51
+ profile_key = profile.respond_to?(:id) ? profile.id : profile
52
+ property_existence(profile_key, @profiles, @other_profiles)
53
+ end
54
+
55
+ def rule_in_profile(rule, profile, rule_key: :title)
56
+ rk = rule.respond_to?(rule_key) ? rule.send(rule_key) : rule
57
+ rules = @profiles.find { |p| p.id == profile }.linked_rule.map(&rk)
58
+ other_rules = @other_profiles.find { |p| p.id == profile }.linked_rule.map(&rk)
59
+ property_existence(rk, rules, other_rules)
60
+ end
61
+
62
+ def added_profiles
63
+ added(@other_profiles.map(&:id), @profiles.map(&:id))
64
+ end
65
+
66
+ def removed_profiles
67
+ removed(@profiles.map(&:id), @other_profiles.map(&:id))
68
+ end
69
+
70
+ def added_rules_by_profile
71
+ @rules_by_profile.each_with_object({}) do |(profile, rules), hsh|
72
+ next unless @other_rules_by_profile.key?(profile)
73
+
74
+ hsh[profile] = added(rules, @other_rules_by_profile[profile])
75
+ end
76
+ end
77
+
78
+ def removed_rules_by_profile
79
+ @rules_by_profile.each_with_object({}) do |(profile, rules), hsh|
80
+ next unless @other_rules_by_profile.key?(profile)
81
+
82
+ hsh[profile] = removed(rules, @other_rules_by_profile[profile])
83
+ end
84
+ end
85
+ end
86
+
87
+ class ProfileRuleContainer
88
+ include ::Comparable
89
+ attr_accessor :prop_checker
90
+ attr_reader :profile, :rules
91
+
92
+ def initialize(profile, prop_checker = nil)
93
+ @profile = profile
94
+ @rules = profile.linked_rule
95
+ @prop_checker = prop_checker
96
+ end
97
+
98
+ def <=>(other)
99
+ @profile.id <=> other.profile.id
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'amatch'
4
+
5
+ module AbideDevUtils
6
+ module XCCDF
7
+ module Diff
8
+ # Diffs benchmark properties.
9
+ module BenchmarkPropertyDiff
10
+ DEFAULT_PROPERTY_DIFF_OPTS = {
11
+ rule_properties_for_similarity: %i[title description rationale fixtext],
12
+ rule_properties_for_confidence: %i[description rationale fixtext],
13
+ rule_confidence_property_threshold: 0.7,
14
+ rule_confidence_total_threshold: 0.5,
15
+ digest_similarity_threshold: 0.75,
16
+ digest_similarity_label_weights: {
17
+ 'title' => 4.0,
18
+ },
19
+ digest_similarity_only_labels: %w[title description fixtext rationale],
20
+ digest_top_x_similarities: 10,
21
+ }.freeze
22
+
23
+ def safe_rule_prop(rule, prop)
24
+ rule.respond_to?(prop) ? rule.send(prop).to_s : :none
25
+ end
26
+
27
+ def self_rule_vals
28
+ @self_rule_vals ||= {}
29
+ end
30
+
31
+ def other_rule_vals
32
+ @other_rule_vals ||= {}
33
+ end
34
+
35
+ def add_rule_val(rule, prop, val, container: nil)
36
+ raise ArgumentError, 'container must not be nil' if container.nil?
37
+
38
+ return unless container.dig(rule, prop).nil?
39
+
40
+ container[rule] ||= {}
41
+ container[rule][prop] = val
42
+ end
43
+
44
+ def add_self_rule_val(rule, prop, val)
45
+ add_rule_val(rule, prop, val, container: self_rule_vals)
46
+ end
47
+
48
+ def add_other_rule_val(rule, prop, val)
49
+ add_rule_val(rule, prop, val, container: other_rule_vals)
50
+ end
51
+
52
+ def same_rule?(prop_similarities)
53
+ confidence_indicator = 0.0
54
+ opts[:rule_properties_for_confidence].each do |prop|
55
+ confidence_indicator += 1.0 if prop_similarities[prop] >= opts[:rule_confidence_property_threshold]
56
+ end
57
+ (confidence_indicator / opts[:rule_properties_for_confidence].length) >= opts[:rule_confidence_total_threshold]
58
+ end
59
+
60
+ def maxed_digest_similarities(child, other_children)
61
+ similarities = other_children.each_with_object([]) do |other_child, ary|
62
+ if other_child.digest_equal? child
63
+ ary << [1.0, other_child]
64
+ next
65
+ end
66
+
67
+ d_sim = child.digest_similarity(other_child,
68
+ only_labels: opts[:digest_similarity_only_labels],
69
+ label_weights: opts[:digest_similarity_label_weights])
70
+ ary << [d_sim, other_child]
71
+ end
72
+ max_digest_similarities(similarities)
73
+ end
74
+
75
+ def max_digest_similarities(digest_similarities)
76
+ digest_similarities.reject! { |s| s[0] < opts[:digest_similarity_threshold] }
77
+ return digest_similarities if digest_similarities.empty?
78
+
79
+ digest_similarities.max_by(opts[:digest_top_x_similarities]) { |s| s[0] }
80
+ end
81
+
82
+ def rule_property_similarity(rule1, rule2)
83
+ prop_similarities = {}
84
+ prop_diff = {}
85
+ opts[:rule_properties_for_similarity].each do |prop|
86
+ add_self_rule_val(rule1, prop, safe_rule_prop(rule1, prop).to_s)
87
+ add_other_rule_val(rule2, prop, safe_rule_prop(rule2, prop).to_s)
88
+ prop_similarities[prop] = self_rule_vals[rule1][prop].levenshtein_similar(other_rule_vals[rule2][prop])
89
+ if prop_similarities[prop] < 1.0
90
+ prop_diff[prop] = { self: self_rule_vals[rule1][prop], other: other_rule_vals[rule2][prop] }
91
+ end
92
+ end
93
+ total = prop_similarities.values.sum / opts[:rule_properties_for_similarity].length
94
+ {
95
+ total: total,
96
+ prop_similarities: prop_similarities,
97
+ prop_diff: prop_diff,
98
+ confident_same: same_rule?(prop_similarities),
99
+ }
100
+ end
101
+
102
+ def most_similar(child, maxed_digest_similarities)
103
+ most_similar_map = maxed_digest_similarities.each_with_object({}) do |similarity, h|
104
+ prop_similarities = rule_property_similarity(child, similarity[1])
105
+ if child.title.to_s == similarity[1].title.to_s
106
+ prop_similarities[:total] = 99.0 # magic number denoting a title match
107
+ end
108
+ h[prop_similarities[:total]] = { self: child, other: similarity[1] }.merge(prop_similarities)
109
+ end
110
+ most_similar_map[most_similar_map.keys.max]
111
+ end
112
+
113
+ def find_most_similar(children, other_children)
114
+ children.each_with_object({}) do |benchmark_child, h|
115
+ maxed_similarities = maxed_digest_similarities(benchmark_child, other_children)
116
+ next if maxed_similarities.empty?
117
+
118
+ best = most_similar(benchmark_child, maxed_similarities)
119
+ next if best.nil? || best.empty?
120
+
121
+ h[benchmark_child] = best
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbideDevUtils
4
+ module XCCDF
5
+ module Diff
6
+ # PropertyExistenceChecker provides methods to check existence state of various properties
7
+ class PropertyExistenceChecker
8
+ def initialize(*_args); end
9
+
10
+ # Compares two arrays (or other iterables implementing `#to_a`)
11
+ # containing properies and returns an array of the properties
12
+ # that are added by other_props but not in self_props.
13
+ def added(self_props, other_props)
14
+ other_props.to_a - self_props.to_a
15
+ end
16
+
17
+ # Compares two arrays (or other iterables implementing `#to_a`)
18
+ # containing properies and returns an array of the properties
19
+ # that are removed by other_props but exist in self_props.
20
+ def removed(this, other)
21
+ this.to_a - other.to_a
22
+ end
23
+
24
+ # Returns a hash of existence states and their inverse.
25
+ def self.inverse_existence_state
26
+ {
27
+ removed: :added,
28
+ added: :removed,
29
+ exists: :exists,
30
+ }
31
+ end
32
+
33
+ private
34
+
35
+ def property_existence(property, self_props, other_props)
36
+ if self_props.include?(property) && !other_props.include?(property)
37
+ :removed
38
+ elsif !self_props.include?(property) && other_props.include?(property)
39
+ :added
40
+ else
41
+ :exists
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end