abide_dev_utils 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,297 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'abide_dev_utils/validate'
5
+ require 'abide_dev_utils/ppt/facter_utils'
6
+
7
+ module AbideDevUtils
8
+ module Ppt
9
+ # Module for working with Hiera
10
+ module Hiera
11
+ INTERP_PATTERN = /%{([^{}]+)}/.freeze
12
+ FACT_PATTERN = /%{facts\.([^{}]+)}/.freeze
13
+ DEFAULT_FACTER_VERSION = '3.14'
14
+ DEFAULT_CONFIG_FILE = 'hiera.yaml'
15
+
16
+ def self.facter_version=(version)
17
+ @facter_version = AbideDevUtils::Ppt::FacterUtils.use_version(version.to_s)
18
+ end
19
+
20
+ def self.facter_version
21
+ @facter_version
22
+ end
23
+
24
+ def self.default_datadir=(dir)
25
+ edir = File.expand_path(dir)
26
+ raise "Dir #{edir} not found" unless File.directory?(edir)
27
+
28
+ @default_datadir = edir
29
+ end
30
+
31
+ def self.default_datadir
32
+ @default_datadir
33
+ end
34
+
35
+ # Represents a Hiera configuration file
36
+ class Config
37
+ def initialize(path = DEFAULT_CONFIG_FILE, facter_version: DEFAULT_FACTER_VERSION)
38
+ @path = File.expand_path(path)
39
+ raise "Hiera config file at path #{@path} not found!" unless File.file?(@path)
40
+
41
+ @conf = YAML.load_file(File.expand_path(path))
42
+ @by_name_path_store = {}
43
+ AbideDevUtils::Ppt::Hiera.default_datadir = @conf['defaults']['datadir'] if @conf['defaults'].key?('datadir')
44
+ AbideDevUtils::Ppt::Hiera.facter_version = facter_version
45
+ end
46
+
47
+ def hierarchy
48
+ @hierarchy ||= Hierarchy.new(@conf['hierarchy'], AbideDevUtils::Ppt::Hiera.default_datadir)
49
+ end
50
+
51
+ def version
52
+ @version ||= @conf['version']
53
+ end
54
+
55
+ def defaults
56
+ @defaults ||= @conf['defaults']
57
+ end
58
+
59
+ def default_datadir
60
+ AbideDevUtils::Ppt::Hiera.default_datadir
61
+ end
62
+
63
+ def default_data_hash
64
+ @default_data_hash ||= defaults['data_hash']
65
+ end
66
+
67
+ def local_hiera_files(hierarchy_name: nil)
68
+ if hierarchy_name
69
+ hierarchy.entry_by_name(hierarchy_name).local_files
70
+ else
71
+ hierarchy.entries.map(&:local_files).flatten
72
+ end
73
+ end
74
+
75
+ def local_hiera_files_with_fact(fact_str, value = nil, hierarchy_name: nil)
76
+ if hierarchy_name
77
+ hierarchy.entry_by_name(hierarchy_name).local_files_with_fact(fact_str, value)
78
+ else
79
+ hierarchy.entries.map { |e| e.local_files_with_fact(fact_str, value) }.flatten
80
+ end
81
+ end
82
+
83
+ def local_hiera_files_with_facts(*fact_arrays, hierarchy_name: nil)
84
+ if hierarchy_name
85
+ hierarchy.entry_by_name(hierarchy_name).local_files_with_facts(*fact_arrays)
86
+ else
87
+ hierarchy.entries.map { |e| e.local_files_with_fact(*fact_arrays) }.flatten
88
+ end
89
+ end
90
+ end
91
+
92
+ # Represents the "hierarchy" section of the Hiera config
93
+ class Hierarchy
94
+ attr_reader :default_datadir, :entries
95
+
96
+ def initialize(hierarchy, default_datadir)
97
+ @hierarchy = hierarchy
98
+ @default_datadir = File.expand_path(default_datadir)
99
+ @entries = @hierarchy.map { |h| HierarchyEntry.new(h) }
100
+ @by_name_store = {}
101
+ @paths_by_name_store = {}
102
+ end
103
+
104
+ def method_missing(m, *args, &block)
105
+ if %i[each each_with_object each_with_index select reject map].include?(m)
106
+ @entries.send(m, *args, &block)
107
+ else
108
+ super
109
+ end
110
+ end
111
+
112
+ def respond_to_missing?(m, include_private = false)
113
+ %i[each each_with_object each_with_index select reject map].include?(m) || super
114
+ end
115
+
116
+ def entry_by_name(name)
117
+ AbideDevUtils::Validate.populated_string(name)
118
+ return @by_name_store[name] if @by_name_store[name]
119
+
120
+ found = @entries.select { |x| x.name == name }
121
+ AbideDevUtils::Validate.not_empty(found, "Hierarchy entry for name '#{name}' not found")
122
+ @by_name_store[name] = found[0]
123
+ @by_name_store[name]
124
+ end
125
+ end
126
+
127
+ # Represents a single entry in the hierarchy
128
+ class HierarchyEntry
129
+ attr_reader :entry, :name, :paths
130
+
131
+ def initialize(entry)
132
+ @entry = entry
133
+ @name = @entry['name']
134
+ @paths = @entry.key?('path') ? create_paths(@entry['path']) : create_paths(*@entry['paths'])
135
+ end
136
+
137
+ def local_files
138
+ @local_files ||= paths.map(&:local_files).flatten
139
+ end
140
+
141
+ def local_files_with_fact(fact_str, value = nil)
142
+ paths.map { |p| p.local_files_with_fact(fact_str, value) }.flatten
143
+ end
144
+
145
+ def local_files_with_facts(*fact_arrays)
146
+ paths.map { |p| p.local_files_with_facts(*fact_arrays) }.flatten
147
+ end
148
+
149
+ def to_s
150
+ name
151
+ end
152
+
153
+ private
154
+
155
+ def create_paths(*paths)
156
+ paths.map { |p| HierarchyEntryPath.new(p) }
157
+ end
158
+ end
159
+
160
+ # Represents a Hiera entry path
161
+ class HierarchyEntryPath
162
+ attr_reader :path
163
+
164
+ def initialize(path)
165
+ @path = path
166
+ end
167
+
168
+ def path_parts
169
+ @path_parts ||= path.split('/')
170
+ end
171
+
172
+ def interpolation
173
+ @interpolation ||= path.scan(INTERP_PATTERN).flatten
174
+ end
175
+
176
+ def interpolation?
177
+ !interpolation.empty?
178
+ end
179
+
180
+ def facts
181
+ @facts ||= path.scan(FACT_PATTERN).flatten
182
+ end
183
+
184
+ def facts?
185
+ !facts.empty?
186
+ end
187
+
188
+ def possible_fact_values
189
+ @possible_fact_values ||= AbideDevUtils::Ppt::FacterUtils.resolve_related_dot_paths(*facts)
190
+ end
191
+
192
+ def local_files
193
+ @local_files ||= find_local_files.flatten
194
+ end
195
+
196
+ def local_files_with_fact(fact_str, value = nil)
197
+ local_files.select do |lf|
198
+ # The match below is case-insentive for convenience
199
+ (value.nil? ? lf.fact_values.key?(fact_str) : (lf.fact_values[fact_str]&.match?(/#{value}/i) || false))
200
+ end
201
+ end
202
+
203
+ def local_files_with_facts(*fact_arrays)
204
+ return local_files_with_fact(*fact_arrays[0]) if fact_arrays.length == 1
205
+
206
+ start_fact = fact_arrays[0][0]
207
+ last_fact = nil
208
+ memo = {}
209
+ with_facts = []
210
+ fact_arrays.each do |fa|
211
+ cur_fact = fa[0]
212
+ memo[cur_fact] = local_files_with_fact(*fa)
213
+ if cur_fact == start_fact
214
+ with_facts = memo[cur_fact]
215
+ else
216
+ last_paths = memo[last_fact].map(&:path)
217
+ cur_paths = memo[cur_fact].map(&:path)
218
+ with_facts.reject! { |x| last_paths.difference(cur_paths).include?(x.path) }
219
+ end
220
+ last_fact = cur_fact
221
+ end
222
+ with_facts.flatten.uniq(&:path)
223
+ end
224
+
225
+ def to_s
226
+ path
227
+ end
228
+
229
+ private
230
+
231
+ def find_local_files
232
+ new_paths = []
233
+ possible_fact_values.each do |pfv|
234
+ new_path = path.dup
235
+ pfv.each do |v|
236
+ next unless v
237
+
238
+ new_path.sub!(FACT_PATTERN, v)
239
+ end
240
+ new_paths << EntryPathLocalFile.new(new_path, facts, possible_fact_values)
241
+ end
242
+ new_paths.uniq(&:path).select(&:exist?)
243
+ end
244
+ end
245
+
246
+ # Represents a local file derived from a Hiera path
247
+ class EntryPathLocalFile
248
+ attr_reader :path, :facts
249
+
250
+ def initialize(path, facts, possible_fact_values)
251
+ @path = File.expand_path(File.join(AbideDevUtils::Ppt::Hiera.default_datadir, path))
252
+ @facts = facts
253
+ @possible_fact_values = possible_fact_values
254
+ end
255
+
256
+ def fact_values
257
+ @fact_values ||= fact_values_for_path
258
+ end
259
+
260
+ def path_parts
261
+ @path_parts ||= path.split('/')
262
+ end
263
+
264
+ def exist?
265
+ File.file?(path)
266
+ end
267
+
268
+ def to_s
269
+ path
270
+ end
271
+
272
+ def to_h
273
+ {
274
+ path: path,
275
+ facts: facts
276
+ }
277
+ end
278
+
279
+ private
280
+
281
+ def fact_values_for_path
282
+ no_fext_path_parts = path_parts.map { |part| File.basename(part, '.yaml') }
283
+ valid_fact_values = @possible_fact_values.select do |pfv|
284
+ pfv.all? { |v| no_fext_path_parts.include?(v) }
285
+ end
286
+ valid_fact_values.uniq! # Removes duplicate arrays, not duplicate fact values
287
+ valid_fact_values.flatten!
288
+ return {} if valid_fact_values.empty?
289
+
290
+ fact_vals = {}
291
+ facts.each_index { |idx| fact_vals[facts[idx]] = valid_fact_values[idx] }
292
+ fact_vals
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+ require 'abide_dev_utils/validate'
6
+ require 'abide_dev_utils/ppt/hiera'
7
+
8
+ module AbideDevUtils
9
+ module Ppt
10
+ # Class for working with Puppet Modules
11
+ class PuppetModule
12
+ DEF_FILES = {
13
+ metadata: 'metadata.json',
14
+ readme: 'README.md',
15
+ reference: 'REFERENCE.md',
16
+ changelog: 'CHANGELOG.md',
17
+ fixtures: '.fixtures.yml',
18
+ rubocop: '.rubocop.yml',
19
+ sync: '.sync.yml',
20
+ pdkignore: '.pdkignore',
21
+ gitignore: '.gitignore'
22
+ }.freeze
23
+
24
+ attr_reader :directory, :special_files
25
+
26
+ def initialize(directory = Dir.pwd)
27
+ AbideDevUtils::Validate.directory(directory)
28
+ @directory = directory
29
+ @special_files = DEF_FILES.dup.transform_values { |v| File.expand_path(File.join(@directory, v)) }
30
+ end
31
+
32
+ def name(strip_namespace: false)
33
+ strip_namespace ? metadata['name'].split('-')[-1] : metadata['name']
34
+ end
35
+
36
+ def metadata
37
+ @metadata ||= JSON.parse(File.read(special_files[:metadata]))
38
+ end
39
+
40
+ def supported_os
41
+ @supported_os ||= find_supported_os
42
+ end
43
+
44
+ def hiera_conf
45
+ @hiera_conf ||= AbideDevUtils::Ppt::Hiera::Config.new
46
+ end
47
+
48
+ private
49
+
50
+ def find_supported_os
51
+ return [] unless metadata['operatingsystem_support']
52
+
53
+ metadata['operatingsystem_support'].each_with_object([]) do |os, arr|
54
+ os['operatingsystemrelease'].each do |r|
55
+ arr << "#{os['operatingsystem']}::#{r}"
56
+ end
57
+ end
58
+ end
59
+
60
+ def in_dir
61
+ return unless block_given?
62
+
63
+ current = Dir.pwd
64
+ if current == File.expand_path(directory)
65
+ yield
66
+ else
67
+ Dir.chdir(directory)
68
+ yield
69
+ Dir.chdir(current)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -4,6 +4,9 @@ require 'abide_dev_utils/output'
4
4
  require 'abide_dev_utils/validate'
5
5
  require 'abide_dev_utils/errors'
6
6
  require 'abide_dev_utils/ppt/class_utils'
7
+ require 'abide_dev_utils/ppt/facter_utils'
8
+ require 'abide_dev_utils/ppt/hiera'
9
+ require 'abide_dev_utils/ppt/puppet_module'
7
10
 
8
11
  module AbideDevUtils
9
12
  module Ppt
@@ -63,11 +66,6 @@ module AbideDevUtils
63
66
  AbideDevUtils::Output.simple('Successfully fixed all classes.')
64
67
  end
65
68
 
66
- def self.generate_coverage_report(puppet_class_dir, hiera_path, profile = nil)
67
- require 'abide_dev_utils/ppt/coverage'
68
- CoverageReport.generate(puppet_class_dir, hiera_path, profile)
69
- end
70
-
71
69
  def self.build_new_object(type, name, opts)
72
70
  require 'abide_dev_utils/ppt/new_obj'
73
71
  AbideDevUtils::Ppt::NewObjectBuilder.new(
@@ -3,7 +3,12 @@
3
3
  require 'abide_dev_utils/errors'
4
4
 
5
5
  module AbideDevUtils
6
+ # Methods used for validating data
6
7
  module Validate
8
+ def self.puppet_module_directory(path = Dir.pwd)
9
+ raise AbideDevUtils::Errors::Ppt::NotModuleDirError, path unless File.file?(File.join(path, 'metadata.json'))
10
+ end
11
+
7
12
  def self.filesystem_path(path)
8
13
  raise AbideDevUtils::Errors::FileNotFoundError, path unless File.exist?(path)
9
14
  end
@@ -22,6 +27,15 @@ module AbideDevUtils
22
27
  raise AbideDevUtils::Errors::PathNotDirectoryError, path unless File.directory?(path)
23
28
  end
24
29
 
30
+ def self.populated_string(thing)
31
+ raise AbideDevUtils::Errors::NotPopulatedStringError, 'Object is nil' if thing.nil?
32
+
33
+ unless thing.instance_of?(String)
34
+ raise AbideDevUtils::Errors::NotPopulatedStringError, "Object is not a String. Type: #{thing.class}"
35
+ end
36
+ raise AbideDevUtils::Errors::NotPopulatedStringError, 'String is empty' if thing.empty?
37
+ end
38
+
25
39
  def self.not_empty(thing, msg)
26
40
  raise AbideDevUtils::Errors::ObjectEmptyError, msg if thing.empty?
27
41
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbideDevUtils
4
- VERSION = "0.10.1"
4
+ VERSION = "0.11.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abide_dev_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - abide-team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-02 00:00:00.000000000 Z
11
+ date: 2022-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0.4'
139
+ - !ruby/object:Gem::Dependency
140
+ name: facterdb
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '1.18'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '1.18'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: bundler
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -345,6 +359,11 @@ files:
345
359
  - exe/abide
346
360
  - lib/abide_dev_utils.rb
347
361
  - lib/abide_dev_utils/cem.rb
362
+ - lib/abide_dev_utils/cem/benchmark.rb
363
+ - lib/abide_dev_utils/cem/coverage_report.rb
364
+ - lib/abide_dev_utils/cem/generate.rb
365
+ - lib/abide_dev_utils/cem/generate/reference.rb
366
+ - lib/abide_dev_utils/cem/mapping/mapper.rb
348
367
  - lib/abide_dev_utils/cli.rb
349
368
  - lib/abide_dev_utils/cli/abstract.rb
350
369
  - lib/abide_dev_utils/cli/cem.rb
@@ -358,6 +377,7 @@ files:
358
377
  - lib/abide_dev_utils/constants.rb
359
378
  - lib/abide_dev_utils/errors.rb
360
379
  - lib/abide_dev_utils/errors/base.rb
380
+ - lib/abide_dev_utils/errors/cem.rb
361
381
  - lib/abide_dev_utils/errors/comply.rb
362
382
  - lib/abide_dev_utils/errors/gcloud.rb
363
383
  - lib/abide_dev_utils/errors/general.rb
@@ -367,13 +387,16 @@ files:
367
387
  - lib/abide_dev_utils/files.rb
368
388
  - lib/abide_dev_utils/gcloud.rb
369
389
  - lib/abide_dev_utils/jira.rb
390
+ - lib/abide_dev_utils/markdown.rb
370
391
  - lib/abide_dev_utils/mixins.rb
371
392
  - lib/abide_dev_utils/output.rb
372
393
  - lib/abide_dev_utils/ppt.rb
373
394
  - lib/abide_dev_utils/ppt/api.rb
374
395
  - lib/abide_dev_utils/ppt/class_utils.rb
375
- - lib/abide_dev_utils/ppt/coverage.rb
396
+ - lib/abide_dev_utils/ppt/facter_utils.rb
397
+ - lib/abide_dev_utils/ppt/hiera.rb
376
398
  - lib/abide_dev_utils/ppt/new_obj.rb
399
+ - lib/abide_dev_utils/ppt/puppet_module.rb
377
400
  - lib/abide_dev_utils/ppt/score_module.rb
378
401
  - lib/abide_dev_utils/prompt.rb
379
402
  - lib/abide_dev_utils/resources/generic_spec.erb
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- require 'pathname'
5
- require 'yaml'
6
- require 'puppet_pal'
7
- require 'abide_dev_utils/ppt/class_utils'
8
-
9
- module AbideDevUtils
10
- module Ppt
11
- class CoverageReport
12
- def self.generate(puppet_class_dir, hiera_path, profile = nil)
13
- coverage = {}
14
- coverage['classes'] = {}
15
- all_cap = ClassUtils.find_all_classes_and_paths(puppet_class_dir)
16
- invalid_classes = find_invalid_classes(all_cap)
17
- valid_classes = find_valid_classes(all_cap, invalid_classes)
18
- coverage['classes']['invalid'] = invalid_classes
19
- coverage['classes']['valid'] = valid_classes
20
- hiera = YAML.safe_load(File.open(hiera_path))
21
- profile&.gsub!(/^profile_/, '') unless profile.nil?
22
-
23
- matcher = profile.nil? ? /^profile_/ : /^profile_#{profile}/
24
- hiera.each do |k, v|
25
- key_base = k.split('::')[-1]
26
- coverage['benchmark'] = v if key_base == 'title'
27
- next unless key_base.match?(matcher)
28
-
29
- coverage[key_base] = generate_uncovered_data(v, valid_classes)
30
- end
31
- coverage
32
- end
33
-
34
- def self.generate_uncovered_data(ctrl_list, valid_classes)
35
- out_hash = {}
36
- out_hash[:num_total] = ctrl_list.length
37
- out_hash[:uncovered] = []
38
- out_hash[:covered] = []
39
- ctrl_list.each do |c|
40
- if valid_classes.include?(c)
41
- out_hash[:covered] << c
42
- else
43
- out_hash[:uncovered] << c
44
- end
45
- end
46
- out_hash[:num_covered] = out_hash[:covered].length
47
- out_hash[:num_uncovered] = out_hash[:uncovered].length
48
- out_hash[:coverage] = Float(
49
- (Float(out_hash[:num_covered]) / Float(out_hash[:num_total])) * 100.0
50
- ).floor(3)
51
- out_hash
52
- end
53
-
54
- def self.find_valid_classes(all_cap, invalid_classes)
55
- all_classes = all_cap.dup.transpose[0]
56
- return [] if all_classes.nil?
57
-
58
- return all_classes - invalid_classes unless invalid_classes.nil?
59
-
60
- all_classes
61
- end
62
-
63
- def self.find_invalid_classes(all_cap)
64
- invalid_classes = []
65
- all_cap.each do |cap|
66
- invalid_classes << cap[0] unless class_valid?(cap[1])
67
- end
68
- invalid_classes
69
- end
70
-
71
- def self.class_valid?(manifest_path)
72
- compiler = Puppet::Pal::Compiler.new(nil)
73
- ast = compiler.parse_file(manifest_path)
74
- ast.body.body.statements.each do |s|
75
- next unless s.respond_to?(:arguments)
76
- next unless s.arguments.respond_to?(:each)
77
-
78
- s.arguments.each do |i|
79
- return false if i.value == 'Not implemented'
80
- end
81
- end
82
- true
83
- end
84
- end
85
- end
86
- end