abide_dev_utils 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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