abide_dev_utils 0.12.2 → 0.14.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.
@@ -2,82 +2,281 @@
2
2
 
3
3
  require 'json'
4
4
  require 'facterdb'
5
+ require_relative '../dot_number_comparable'
5
6
 
6
7
  module AbideDevUtils
7
8
  module Ppt
8
9
  # Methods relating to Facter
9
10
  module FacterUtils
10
- class << self
11
- attr_writer :current_version
11
+ # A set of facts for a specific Facter version
12
+ class FactSet
13
+ include AbideDevUtils::DotNumberComparable
12
14
 
13
- def current_version
14
- return latest_version unless defined?(@current_version)
15
+ attr_reader :facter_version, :facts
15
16
 
16
- @current_version
17
+ def initialize(facter_version)
18
+ @facter_version = facter_version.to_s
19
+ @facts = []
17
20
  end
21
+ alias number facter_version # for DotNumberComparable
18
22
 
19
- def use_version(version)
20
- self.current_version = version
21
- current_version
23
+ def load_facts(fact_file)
24
+ @facts << JSON.parse(File.read(fact_file))
22
25
  end
23
26
 
24
- def with_version(version, reset: true)
25
- return unless block_given?
27
+ def find_by_fact_value_tuples(*fact_val_tuples)
28
+ facts.each do |f|
29
+ results = fact_val_tuples.each_with_object([]) do |fvt, ary|
30
+ ary << true if f.dig(*fvt[0].delete_prefix('facts').split('.')) == fvt[1]
31
+ end
32
+ return f if results.size == fact_val_tuples.size
33
+ end
34
+ nil
35
+ end
36
+
37
+ def find_by_fact_value_tuple(fact_val_tuple)
38
+ fact_name, fact_val = fact_val_tuple
39
+ @facts.find { |f| f.dig(*fact_name.delete_prefix('facts').split('.')) == fact_val }
40
+ end
41
+
42
+ def dot_dig(dot_path)
43
+ result = @facts.map { |f| f.dig(*dot_path.delete_prefix('facts').split('.')) }
44
+ return nil if result.empty?
45
+
46
+ result
47
+ end
48
+
49
+ def dig(*args)
50
+ result = @facts.map { |f| f.dig(*args) }
51
+ return nil if result.empty?
52
+
53
+ result
54
+ end
55
+ end
56
+
57
+ class FactSets
58
+ REQUIRED_FACTERDB_VERSION = '1.21.0'
59
+
60
+ def initialize
61
+ check_facterdb_version
62
+ @fact_val_tuple_cache = {}
63
+ @dot_dig_cache = {}
64
+ @dot_dig_related_cache = {}
65
+ end
66
+
67
+ def fact_sets
68
+ @fact_sets ||= FacterDB.facterdb_fact_files.each_with_object({}) do |f, h|
69
+ facter_version = File.basename(File.dirname(f))
70
+ fact_set = h[facter_version] || FactSet.new(facter_version)
71
+ fact_set.load_facts(f)
72
+ h[facter_version] = fact_set unless h.key?(facter_version)
73
+ end
74
+ end
26
75
 
27
- old_ver = current_version.dup
28
- use_version(version)
29
- output = yield
30
- use_version(old_ver) if reset
31
- output
76
+ def fact_set(facter_version)
77
+ fact_sets[facter_version]
32
78
  end
33
79
 
34
- def fact_files
35
- @fact_files ||= FacterDB.facterdb_fact_files.each_with_object({}) do |f, h|
36
- facter_version = file_facter_version(f)
37
- h[facter_version] = [] unless h.key?(facter_version)
38
- h[facter_version] << f
80
+ def facter_versions
81
+ @facter_versions ||= fact_sets.keys.sort
82
+ end
83
+
84
+ def find_by_fact_value_tuples(*fact_val_tuples)
85
+ fact_sets.each do |_, fs|
86
+ result = fs.find_by_fact_value_tuples(*fact_val_tuples)
87
+ return result unless result.nil? || result.empty?
88
+ end
89
+ nil
90
+ end
91
+
92
+ def find_by_fact_value_tuple(fact_val_tuple)
93
+ ck = cache_key(*fact_val_tuple)
94
+ return @fact_val_tuple_cache[ck] if @fact_val_tuple_cache.key?(ck)
95
+
96
+ facter_versions.each do |v|
97
+ result = fact_set(v).find_by_fact_value_tuple(fact_val_tuple)
98
+ next if result.nil? || result.empty?
99
+
100
+ @fact_val_tuple_cache[ck] = result
101
+ return result
102
+ end
103
+
104
+ @fact_val_tuple_cache[ck] = nil
105
+ end
106
+
107
+ def dot_dig(dot_path, facter_version: latest_version, recurse: true)
108
+ ck = cache_key(dot_path, facter_version, recurse)
109
+ return @dot_dig_cache[ck] if @dot_dig_cache.key?(ck)
110
+
111
+ result = fact_set(facter_version).dot_dig(dot_path)
112
+ unless result.nil? && recurse
113
+ @dot_dig_cache[ck] = result
114
+ return result
39
115
  end
116
+
117
+ previous = previous_version(facter_version)
118
+ unless previous
119
+ @dot_dig_cache[ck] = result
120
+ return result
121
+ end
122
+
123
+ result = dot_dig(dot_path, facter_version: previous, recurse: true)
124
+ @dot_dig_cache[ck] = result
125
+ result
40
126
  end
127
+ alias resolve_dot_path dot_dig
41
128
 
42
- def fact_sets(facter_version: current_version)
43
- @fact_sets ||= fact_files[facter_version].each_with_object({}) do |fp, h|
44
- h[facter_version] = [] unless h.key?(facter_version)
45
- h[facter_version] << JSON.parse(File.read(fp))
129
+ def dot_dig_related(*dot_paths, facter_version: latest_version, recurse: true)
130
+ ck = cache_key(*dot_paths, facter_version, recurse)
131
+ return @dot_dig_related_cache[ck] if @dot_dig_related_cache.key?(ck)
132
+
133
+ result = []
134
+ fact_sets[facter_version].facts.map do |f|
135
+ result << dot_paths.map { |p| f.dig(*p.delete_prefix('facts').split('.')) }
136
+ end
137
+ unless recurse
138
+ @dot_dig_related_cache[ck] = result.compact.uniq
139
+ return @dot_dig_related_cache[ck]
46
140
  end
141
+
142
+ previous = previous_version(facter_version)
143
+ unless previous
144
+ @dot_dig_related_cache[ck] = result.compact.uniq
145
+ return @dot_dig_related_cache[ck]
146
+ end
147
+
148
+ res = result + dot_dig_related(*dot_paths, facter_version: previous, recurse: true)
149
+ @dot_dig_related_cache[ck] = res.compact.uniq
150
+ @dot_dig_related_cache[ck]
47
151
  end
152
+ alias resolve_related_dot_paths dot_dig_related
48
153
 
49
- def file_facter_version(path)
50
- File.basename(File.dirname(path))
154
+ private
155
+
156
+ def check_facterdb_version
157
+ require 'facterdb/version'
158
+ return if Gem::Version.new(FacterDB::Version::STRING) >= Gem::Version.new(REQUIRED_FACTERDB_VERSION)
159
+
160
+ warn "FacterDB version #{FacterDB::Version::STRING} is too old. Please upgrade to 1.21.0 or later."
161
+ warn 'FacterUtils may not work correctly or at all.'
162
+ end
163
+
164
+ def cache_key(*args)
165
+ args.map(&:to_s).join('_')
166
+ end
167
+
168
+ def latest_version
169
+ facter_versions.last
170
+ end
171
+
172
+ def previous_version(facter_version)
173
+ index = facter_versions.index(facter_version)
174
+ return unless index&.positive?
175
+
176
+ facter_versions[index - 1]
177
+ end
178
+ end
179
+
180
+ class << self
181
+ # attr_writer :current_version
182
+
183
+ # def current_version
184
+ # return latest_version unless defined?(@current_version)
185
+
186
+ # @current_version
187
+ # end
188
+
189
+ # def use_version(version)
190
+ # self.current_version = version
191
+ # current_version
192
+ # end
193
+
194
+ # def with_version(version, reset: true)
195
+ # return unless block_given?
196
+
197
+ # old_ver = current_version.dup
198
+ # use_version(version)
199
+ # output = yield
200
+ # use_version(old_ver) if reset
201
+ # output
202
+ # end
203
+
204
+ # def fact_files
205
+ # FacterDB.facterdb_fact_files.each_with_object({}) do |f, h|
206
+ # facter_version = file_facter_version(f)
207
+ # h[facter_version] = [] unless h.key?(facter_version)
208
+ # h[facter_version] << f
209
+ # end
210
+ # end
211
+
212
+ # def fact_sets(facter_version: current_version)
213
+ # fact_files[facter_version].each_with_object({}) do |fp, h|
214
+ # h[facter_version] = [] unless h.key?(facter_version)
215
+ # h[facter_version] << JSON.parse(File.read(fp))
216
+ # end
217
+ # end
218
+
219
+ # def file_facter_version(path)
220
+ # File.basename(File.dirname(path))
221
+ # end
222
+
223
+ # def all_versions
224
+ # fact_files.keys.sort
225
+ # end
226
+
227
+ # def latest_version
228
+ # all_versions[-1]
229
+ # end
230
+
231
+ def fact_sets
232
+ @fact_sets ||= FacterDB.facterdb_fact_files.each_with_object([]) do |f, ary|
233
+ facter_version = File.basename(File.dirname(f))
234
+ fact_set = ary.find { |fs| fs.facter_version == facter_version }
235
+ fact_set ||= FactSet.new(facter_version)
236
+ fact_set.load_facts(f)
237
+ ary << fact_set unless ary.include?(fact_set)
238
+ end
51
239
  end
52
240
 
53
241
  def all_versions
54
- @all_versions ||= fact_files.keys.sort
242
+ @all_versions ||= fact_sets.sort.map(&:facter_version)
55
243
  end
56
244
 
57
245
  def latest_version
58
- @latest_version ||= all_versions[-1]
246
+ @latest_version ||= all_versions.last
59
247
  end
60
248
 
61
- def previous_major_version(facter_version = current_version)
62
- @previous_major_version_map ||= {}
249
+ def with_version(version)
250
+ return unless block_given?
63
251
 
64
- majver = facter_version.split('.')[0]
65
- return @previous_major_version_map[majver] if @previous_major_version_map.key?(majver)
252
+ fact_set = fact_sets.find { |fs| fs.facter_version == version }
253
+ raise "No facts found for version #{version}" unless fact_set
254
+
255
+ yield fact_set
256
+ end
66
257
 
258
+ def previous_major_version(facter_version = latest_version)
259
+ majver = facter_version.split('.')[0]
67
260
  prev_majver = (majver.to_i - 1).to_s
68
261
  prev_ver = all_versions.select { |v| v.start_with?(prev_majver) }.max
69
262
  return nil if prev_ver.to_i < 1
70
263
 
71
- @previous_major_version_map[majver] = prev_ver
72
- @previous_major_version_map[majver]
264
+ prev_ver
73
265
  end
74
266
 
75
- def recurse_versions(version = current_version, &block)
76
- use_version(version)
77
- output = yield
267
+ def previous_version(facter_version = latest_version)
268
+ reversed = all_versions.reverse
269
+ rev_index = reversed.index(facter_version)
270
+ return nil if rev_index.nil? || rev_index == reversed.length - 1
271
+
272
+ reversed[reversed.index(facter_version) + 1]
273
+ end
274
+
275
+ def recurse_versions(version = latest_version, &block)
276
+ output = yield version
78
277
  return output unless output.nil? || output.empty?
79
278
 
80
- prev_ver = previous_major_version(version).dup
279
+ prev_ver = previous_version(version).dup
81
280
  return nil if prev_ver.nil?
82
281
 
83
282
  recurse_versions(prev_ver, &block)
@@ -89,47 +288,49 @@ module AbideDevUtils
89
288
  raise "Failed to find output while recursing versions. Locals: #{locals}"
90
289
  end
91
290
 
92
- def recursive_facts_for_os(os_name, os_release_major = nil, os_hardware: 'x86_64')
93
- saved_ver = current_version.dup
94
- output = recurse_versions do
95
- facts_for_os(os_name, os_release_major, os_hardware: os_hardware)
291
+ def recursive_facts_for_os(os_name, os_release_major = nil, facter_version: latest_version)
292
+ recurse_versions(facter_version) do |ver|
293
+ facts_for_os(os_name, os_release_major, facter_version: ver)
96
294
  end
97
- use_version(saved_ver)
98
- output
99
295
  end
100
296
 
101
- def facts_for_os(os_name, os_release_major = nil, os_hardware: 'x86_64', facter_version: current_version)
102
- cache_key = "#{os_name.downcase}_#{os_release_major}_#{os_hardware}"
103
- return @facts_for_os[cache_key] if @facts_for_os&.key?(cache_key)
104
-
105
- fact_file = fact_files[facter_version].find do |f|
106
- name_parts = File.basename(f, '.facts').split('-')
107
- name = name_parts[0]
108
- relmaj = name_parts.length >= 3 ? name_parts[1] : nil
109
- hardware = name_parts[-1]
110
- name == os_name.downcase && relmaj == os_release_major && hardware == os_hardware
111
- end
112
- return if fact_file.nil? || fact_file.empty?
113
-
114
- @facts_for_os = {} unless defined?(@facts_for_os)
115
- @facts_for_os[cache_key] = JSON.parse(File.read(fact_file))
116
- @facts_for_os[cache_key]
117
- end
297
+ # def facts_for_os(os_name, os_release_major = nil, facter_version: latest_version)
298
+ # fact_sets..find do |f|
299
+ # f['os']['name'] == os_name && f['os']['release']['major'] == os_release_major.to_s
300
+ # end
301
+ # end
118
302
 
119
- def resolve_dot_path(dot_path, facter_version: latest_version)
303
+ def resolve_dot_path(dot_path, facter_version: latest_version, strict: false)
120
304
  path_array = dot_path.delete_prefix('facts.').split('.')
121
- resolved = fact_sets[facter_version].map do |fs|
122
- fs.dig(*path_array)
123
- end
124
- resolved.compact.uniq
305
+ resolved = if strict
306
+ fact_sets[facter_version].map do |fs|
307
+ fs.dig(*path_array)
308
+ end
309
+ else
310
+ recurse_versions(facter_version) do |ver|
311
+ fact_sets[ver].map { |fs| fs.dig(*path_array) }
312
+ end
313
+ end
314
+ resolved&.compact&.uniq
125
315
  end
126
316
 
127
- def resolve_related_dot_paths(*dot_paths, facter_version: current_version)
317
+ def resolve_related_dot_paths(*dot_paths, facter_version: latest_version, strict: false)
128
318
  resolved = []
129
- fact_sets[facter_version].map do |fs|
130
- resolved << dot_paths.map do |p|
131
- path_array = p.delete_prefix('facts.').split('.')
132
- fs.dig(*path_array)
319
+ if strict
320
+ fact_sets[facter_version].map do |fs|
321
+ resolved << dot_paths.map do |p|
322
+ path_array = p.delete_prefix('facts.').split('.')
323
+ fs.dig(*path_array)
324
+ end
325
+ end
326
+ else
327
+ recurse_versions(facter_version) do |ver|
328
+ fact_sets[ver].map do |fs|
329
+ resolved << dot_paths.map do |p|
330
+ path_array = p.delete_prefix('facts.').split('.')
331
+ fs.dig(*path_array)
332
+ end
333
+ end
133
334
  end
134
335
  end
135
336
  resolved
@@ -10,7 +10,7 @@ module AbideDevUtils
10
10
  module Hiera
11
11
  INTERP_PATTERN = /%{([^{}]+)}/.freeze
12
12
  FACT_PATTERN = /%{facts\.([^{}]+)}/.freeze
13
- DEFAULT_FACTER_VERSION = '3.14'
13
+ DEFAULT_FACTER_VERSION = '4.2'
14
14
  DEFAULT_CONFIG_FILE = 'hiera.yaml'
15
15
 
16
16
  def self.facter_version=(version)
@@ -18,7 +18,7 @@ module AbideDevUtils
18
18
  end
19
19
 
20
20
  def self.facter_version
21
- @facter_version
21
+ @facter_version ||= AbideDevUtils::Ppt::FacterUtils.latest_version
22
22
  end
23
23
 
24
24
  def self.default_datadir=(dir)
@@ -41,7 +41,7 @@ module AbideDevUtils
41
41
  @root_dir = File.dirname(@path)
42
42
  @conf = YAML.load_file(File.expand_path(path))
43
43
  @by_name_path_store = {}
44
- AbideDevUtils::Ppt::Hiera.facter_version = facter_version
44
+ #AbideDevUtils::Ppt::Hiera.facter_version = facter_version
45
45
  if @conf['defaults'].key?('datadir')
46
46
  AbideDevUtils::Ppt::Hiera.default_datadir = File.join(@root_dir, @conf['defaults']['datadir'])
47
47
  end
@@ -166,6 +166,7 @@ module AbideDevUtils
166
166
 
167
167
  def initialize(path)
168
168
  @path = path
169
+ @fact_sets = AbideDevUtils::Ppt::FacterUtils::FactSets.new
169
170
  end
170
171
 
171
172
  def path_parts
@@ -189,7 +190,7 @@ module AbideDevUtils
189
190
  end
190
191
 
191
192
  def possible_fact_values
192
- @possible_fact_values ||= AbideDevUtils::Ppt::FacterUtils.resolve_related_dot_paths(*facts)
193
+ @possible_fact_values ||= @fact_sets.resolve_related_dot_paths(*facts)
193
194
  end
194
195
 
195
196
  def local_files
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppet-strings'
4
+ require 'puppet-strings/yard'
5
+
6
+ module AbideDevUtils
7
+ module Ppt
8
+ # Puppet Strings reference object
9
+ class Strings
10
+ REGISTRY_TYPES = %i[
11
+ root
12
+ module
13
+ class
14
+ puppet_class
15
+ puppet_data_type
16
+ puppet_data_type_alias
17
+ puppet_defined_type
18
+ puppet_type
19
+ puppet_provider
20
+ puppet_function
21
+ puppet_task
22
+ puppet_plan
23
+ ].freeze
24
+
25
+ attr_reader :search_patterns
26
+
27
+ def initialize(search_patterns: nil, **opts)
28
+ check_yardoc_dir
29
+ @search_patterns = search_patterns || PuppetStrings::DEFAULT_SEARCH_PATTERNS
30
+ @debug = opts[:debug]
31
+ @quiet = opts[:quiet]
32
+ PuppetStrings::Yard.setup!
33
+ YARD::CLI::Yardoc.run(*yard_args(@search_patterns, debug: @debug, quiet: @quiet))
34
+ end
35
+
36
+ def debug?
37
+ !!@debug
38
+ end
39
+
40
+ def quiet?
41
+ !!@quiet
42
+ end
43
+
44
+ def registry
45
+ @registry ||= YARD::Registry.all(*REGISTRY_TYPES).map { |i| YardObjectWrapper.new(i) }
46
+ end
47
+
48
+ def find_resource(resource_name)
49
+ to_h.each do |_, resources|
50
+ res = resources.find { |r| r[:name] == resource_name.to_sym }
51
+ return res if res
52
+ end
53
+ end
54
+
55
+ def puppet_classes(hashed: false)
56
+ reg_type(:puppet_class, hashed: hashed)
57
+ end
58
+
59
+ def data_types(hashed: false)
60
+ reg_type(:puppet_data_types, hashed: hashed)
61
+ end
62
+ alias puppet_data_type data_types
63
+
64
+ def data_type_aliases(hashed: false)
65
+ reg_type(:puppet_data_type_alias, hashed: hashed)
66
+ end
67
+ alias puppet_data_type_alias data_type_aliases
68
+
69
+ def defined_types(hashed: false)
70
+ reg_type(:puppet_defined_type, hashed: hashed)
71
+ end
72
+ alias puppet_defined_type defined_types
73
+
74
+ def resource_types(hashed: false)
75
+ reg_type(:puppet_type, hashed: hashed)
76
+ end
77
+ alias puppet_type resource_types
78
+
79
+ def providers(hashed: false)
80
+ reg_type(:puppet_provider, hashed: hashed)
81
+ end
82
+ alias puppet_provider providers
83
+
84
+ def puppet_functions(hashed: false)
85
+ reg_type(:puppet_function, hashed: hashed)
86
+ end
87
+ alias puppet_function puppet_functions
88
+
89
+ def puppet_tasks(hashed: false)
90
+ reg_type(:puppet_task, hashed: hashed)
91
+ end
92
+ alias puppet_task puppet_tasks
93
+
94
+ def puppet_plans(hashed: false)
95
+ reg_type(:puppet_plan, hashed: hashed)
96
+ end
97
+ alias puppet_plan puppet_plans
98
+
99
+ def to_h
100
+ {
101
+ puppet_classes: puppet_classes,
102
+ data_types: data_types,
103
+ data_type_aliases: data_type_aliases,
104
+ defined_types: defined_types,
105
+ resource_types: resource_types,
106
+ providers: providers,
107
+ puppet_functions: puppet_functions,
108
+ puppet_tasks: puppet_tasks,
109
+ puppet_plans: puppet_plans,
110
+ }
111
+ end
112
+
113
+ private
114
+
115
+ def check_yardoc_dir
116
+ yardoc_dir = File.expand_path('./.yardoc')
117
+ return unless Dir.exist?(yardoc_dir) && !File.writable?(yardoc_dir)
118
+
119
+ raise "yardoc directory permissions error. Ensure #{yardoc_dir} is writable by current user."
120
+ end
121
+
122
+ def reg_type(reg_type, hashed: false)
123
+ hashed ? hashes_for_reg_type(reg_type) : select_by_reg_type(reg_type)
124
+ end
125
+
126
+ def select_by_reg_type(reg_type)
127
+ registry.select { |i| i.type == reg_type }
128
+ end
129
+
130
+ def hashes_for_reg_type(reg_type)
131
+ all_to_h(select_by_reg_type(reg_type))
132
+ end
133
+
134
+ def all_to_h(objects)
135
+ objects.sort_by(&:name).map(&:to_hash)
136
+ end
137
+
138
+ def yard_args(patterns, debug: false, quiet: false)
139
+ args = ['doc', '--no-progress', '-n']
140
+ args << '--debug' if debug && !quiet
141
+ args << '--backtrace' if debug && !quiet
142
+ args << '-q' if quiet
143
+ args << '--no-stats' if quiet
144
+ args += patterns
145
+ args
146
+ end
147
+ end
148
+
149
+ # Wrapper class for Yard objects that allows associating things like validators with them
150
+ class YardObjectWrapper
151
+ attr_accessor :validator
152
+ attr_reader :object
153
+
154
+ def initialize(object, validator: nil)
155
+ @object = object
156
+ @validator = validator
157
+ end
158
+
159
+ def method_missing(method, *args, &block)
160
+ if object.respond_to?(method)
161
+ object.send(method, *args, &block)
162
+ elsif validator.respond_to?(method)
163
+ validator.send(method, *args, &block)
164
+ else
165
+ super
166
+ end
167
+ end
168
+
169
+ def respond_to_missing?(method, include_private = false)
170
+ object.respond_to?(method) || validator.respond_to?(method) || super
171
+ end
172
+
173
+ def to_hash
174
+ object.to_hash
175
+ end
176
+ alias to_h to_hash
177
+
178
+ def to_s
179
+ object.to_s
180
+ end
181
+ end
182
+ end
183
+ end
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'abide_dev_utils/output'
4
- require 'abide_dev_utils/validate'
5
- require 'abide_dev_utils/errors'
6
- require 'abide_dev_utils/ppt/api'
7
- require 'abide_dev_utils/ppt/code_gen'
8
- require 'abide_dev_utils/ppt/code_introspection'
9
- require 'abide_dev_utils/ppt/class_utils'
10
- require 'abide_dev_utils/ppt/facter_utils'
11
- require 'abide_dev_utils/ppt/hiera'
12
- require 'abide_dev_utils/ppt/puppet_module'
3
+ require_relative 'output'
4
+ require_relative 'validate'
5
+ require_relative 'errors'
6
+ require_relative 'ppt/api'
7
+ require_relative 'ppt/code_gen'
8
+ require_relative 'ppt/code_introspection'
9
+ require_relative 'ppt/class_utils'
10
+ require_relative 'ppt/facter_utils'
11
+ require_relative 'ppt/hiera'
12
+ require_relative 'ppt/puppet_module'
13
13
 
14
14
  module AbideDevUtils
15
15
  module Ppt