abide_dev_utils 0.13.0 → 0.14.1

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