compliance_engine 0.1.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,426 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+ require 'compliance_engine/version'
5
+ require 'compliance_engine/component'
6
+ require 'compliance_engine/ce'
7
+ require 'compliance_engine/check'
8
+ require 'compliance_engine/control'
9
+ require 'compliance_engine/profile'
10
+ require 'compliance_engine/collection'
11
+ require 'compliance_engine/ces'
12
+ require 'compliance_engine/checks'
13
+ require 'compliance_engine/controls'
14
+ require 'compliance_engine/profiles'
15
+
16
+ require 'compliance_engine/data_loader'
17
+ require 'compliance_engine/data_loader/json'
18
+ require 'compliance_engine/data_loader/yaml'
19
+ require 'compliance_engine/module_loader'
20
+ require 'compliance_engine/environment_loader'
21
+
22
+ require 'deep_merge'
23
+ require 'json'
24
+
25
+ # Work with compliance data
26
+ class ComplianceEngine::Data
27
+ # @param [Array<String>] paths The paths to the compliance data files
28
+ # @param [Hash] facts The facts to use while evaluating the data
29
+ # @param [Integer] enforcement_tolerance The tolerance to use while evaluating the data
30
+ def initialize(*paths, facts: nil, enforcement_tolerance: nil)
31
+ @data ||= {}
32
+ @facts = facts
33
+ @enforcement_tolerance = enforcement_tolerance
34
+ open(*paths) unless paths.nil? || paths.empty?
35
+ end
36
+
37
+ # Setting any of these should all invalidate any cached data
38
+ attr_reader :data, :facts, :enforcement_tolerance, :environment_data, :modulepath
39
+
40
+ # Set the object data
41
+ # @param [Hash] data The data to initialize the object with
42
+ def data=(value)
43
+ @data = value
44
+ invalidate_cache
45
+ end
46
+
47
+ # Set the facts
48
+ # @param [Hash] facts The facts to initialize the object with
49
+ def facts=(value)
50
+ @facts = value
51
+ invalidate_cache
52
+ end
53
+
54
+ # Set the enforcement tolerance
55
+ # @param [Hash] enforcement_tolerance The enforcement tolerance to initialize
56
+ def enforcement_tolerance=(value)
57
+ @enforcement_tolerance = value
58
+ invalidate_cache
59
+ end
60
+
61
+ # Set the environment data
62
+ # @param [Hash] environment_data The environment data to initialize the object with
63
+ def environment_data=(value)
64
+ @environment_data = value
65
+ invalidate_cache
66
+ end
67
+
68
+ # Set the modulepath
69
+ # @param [Array<String>] modulepath The Puppet modulepath
70
+ def modulepath=(value)
71
+ @modulepath = value
72
+ invalidate_cache
73
+ end
74
+
75
+ # Invalidate the cache of computed data
76
+ #
77
+ # @return [NilClass]
78
+ def invalidate_cache
79
+ collection_variables.each { |var| instance_variable_get(var)&.invalidate_cache(self) }
80
+ cache_variables.each { |var| instance_variable_set(var, nil) }
81
+ end
82
+
83
+ # Discard all parsed data other than the top-level data
84
+ #
85
+ # @return [NilClass]
86
+ def reset_collection
87
+ # Discard any cached objects
88
+ (instance_variables - (data_variables + context_variables)).each { |var| instance_variable_set(var, nil) }
89
+ end
90
+
91
+ # Scan a Puppet environment from a zip file
92
+ # @param [String] path The Puppet environment archive file
93
+ # @return [NilClass]
94
+ def open_environment_zip(path)
95
+ require 'compliance_engine/environment_loader/zip'
96
+
97
+ environment = ComplianceEngine::EnvironmentLoader::Zip.new(path)
98
+ self.modulepath = environment.modulepath
99
+ open(environment)
100
+ end
101
+
102
+ # Scan a Puppet environment
103
+ # @param [Array<String>] paths The Puppet modulepath components
104
+ # @return [NilClass]
105
+ def open_environment(*paths)
106
+ environment = ComplianceEngine::EnvironmentLoader.new(*paths)
107
+ self.modulepath = environment.modulepath
108
+ open(environment)
109
+ end
110
+
111
+ # Scan paths for compliance data files
112
+ #
113
+ # @param [Array<String>] paths The paths to the compliance data files
114
+ # @param [Class] fileclass The class to use for reading files
115
+ # @param [Class] dirclass The class to use for reading directories
116
+ # @return [NilClass]
117
+ def open(*paths, fileclass: File, dirclass: Dir)
118
+ modules = {}
119
+
120
+ paths.each do |path|
121
+ if path.is_a?(ComplianceEngine::EnvironmentLoader)
122
+ open(*path.modules)
123
+ next
124
+ end
125
+
126
+ if path.is_a?(ComplianceEngine::ModuleLoader)
127
+ modules[path.name] = path.version unless path.name.nil?
128
+ path.files.each do |file_loader|
129
+ update(file_loader)
130
+ end
131
+ next
132
+ end
133
+
134
+ if path.is_a?(ComplianceEngine::DataLoader)
135
+ update(path, key: path.key, fileclass: fileclass)
136
+ next
137
+ end
138
+
139
+ if fileclass.file?(path)
140
+ key = if Object.const_defined?(:Zip) && path.is_a?(Zip::Entry)
141
+ File.join(path.zipfile.to_s, '.', path.to_s)
142
+ else
143
+ path.to_s
144
+ end
145
+ update(path, key: key, fileclass: fileclass)
146
+ next
147
+ end
148
+
149
+ if fileclass.directory?(path)
150
+ open(ComplianceEngine::ModuleLoader.new(path, fileclass: fileclass, dirclass: dirclass))
151
+ next
152
+ end
153
+
154
+ raise ComplianceEngine::Error, "Invalid path or object '#{path}'"
155
+ end
156
+
157
+ self.environment_data ||= {}
158
+ self.environment_data = self.environment_data.merge(modules)
159
+
160
+ nil
161
+ end
162
+
163
+ # Update the data for a given file
164
+ #
165
+ # @param [String] file The path to the compliance data file
166
+ # @param [String] key The key to use for the data
167
+ # @param [Class] fileclass The class to use for reading files
168
+ # @param [Integer] size The size of the file
169
+ # @param [Time] mtime The modification time of the file
170
+ # @return [NilClass]
171
+ def update(
172
+ filename,
173
+ key: filename.to_s,
174
+ fileclass: File
175
+ )
176
+ if filename.is_a?(String)
177
+ data[key] ||= {}
178
+
179
+ if data[key]&.key?(:loader) && data[key][:loader]
180
+ data[key][:loader].refresh if data[key][:loader].respond_to?(:refresh)
181
+ return
182
+ end
183
+
184
+ loader = if File.extname(filename) == '.json'
185
+ ComplianceEngine::DataLoader::Json.new(filename, fileclass: fileclass, key: key)
186
+ else
187
+ ComplianceEngine::DataLoader::Yaml.new(filename, fileclass: fileclass, key: key)
188
+ end
189
+
190
+ loader.add_observer(self, :update)
191
+ data[key] = {
192
+ loader: loader,
193
+ version: ComplianceEngine::Version.new(loader.data['version']),
194
+ content: loader.data,
195
+ }
196
+ else
197
+ data[filename.key] ||= {}
198
+
199
+ # Assume filename is a loader object
200
+ unless data[filename.key]&.key?(:loader)
201
+ data[filename.key][:loader] = filename
202
+ data[filename.key][:loader].add_observer(self, :update)
203
+ end
204
+ data[filename.key][:version] = ComplianceEngine::Version.new(filename.data['version'])
205
+ data[filename.key][:content] = filename.data
206
+ end
207
+
208
+ reset_collection
209
+ rescue => e
210
+ warn e.message
211
+ end
212
+
213
+ # Get a list of files with compliance data
214
+ #
215
+ # @return [Array<String>]
216
+ def files
217
+ return @files unless @files.nil?
218
+ @files = data.select { |_, file| file.key?(:content) }.keys
219
+ end
220
+
221
+ # Get the compliance data for a given file
222
+ #
223
+ # @param [String] file The path to the compliance data file
224
+ # @return [Hash]
225
+ def get(file)
226
+ data[file][:content]
227
+ rescue
228
+ nil
229
+ end
230
+
231
+ # Return a profile collection
232
+ #
233
+ # @return [ComplianceEngine::Profiles]
234
+ def profiles
235
+ @profiles ||= ComplianceEngine::Profiles.new(self)
236
+ end
237
+
238
+ # Return a collection of CEs
239
+ #
240
+ # @return [ComplianceEngine::CEs]
241
+ def ces
242
+ @ces ||= ComplianceEngine::Ces.new(self)
243
+ end
244
+
245
+ # Return a collection of checks
246
+ #
247
+ # @return [ComplianceEngine::Checks]
248
+ def checks
249
+ @checks ||= ComplianceEngine::Checks.new(self)
250
+ end
251
+
252
+ # Return a collection of controls
253
+ #
254
+ # @return [ComplianceEngine::Controls]
255
+ def controls
256
+ @controls ||= ComplianceEngine::Controls.new(self)
257
+ end
258
+
259
+ # Return all confines
260
+ #
261
+ # @return [Hash]
262
+ def confines
263
+ return @confines unless @confines.nil?
264
+
265
+ @confines ||= {}
266
+
267
+ [profiles, ces, checks, controls].each do |collection|
268
+ collection.each_value do |v|
269
+ v.to_a.each do |component|
270
+ next unless component.key?('confine')
271
+ @confines = @confines.deep_merge!(component['confine'])
272
+ end
273
+ end
274
+ end
275
+
276
+ @confines
277
+ end
278
+
279
+ # Return all Hiera data from checks that map to the requested profiles
280
+ #
281
+ # @param [Array<String>] requested_profiles The requested profiles
282
+ # @return [Hash]
283
+ def hiera(requested_profiles = [])
284
+ # If we have no valid profiles, we won't have any hiera data.
285
+ return {} if requested_profiles.empty?
286
+
287
+ cache_key = requested_profiles.to_s
288
+
289
+ @hiera ||= {}
290
+
291
+ return @hiera[cache_key] if @hiera.key?(cache_key)
292
+
293
+ valid_profiles = []
294
+ requested_profiles.each do |profile|
295
+ if profiles[profile].nil?
296
+ warn "Requested profile '#{profile}' not defined"
297
+ next
298
+ end
299
+
300
+ valid_profiles << profiles[profile]
301
+ end
302
+
303
+ # If we have no valid profiles, we won't have any hiera data.
304
+ if valid_profiles.empty?
305
+ @hiera[cache_key] = {}
306
+ return @hiera[cache_key]
307
+ end
308
+
309
+ parameters = {}
310
+
311
+ valid_profiles.reverse_each do |profile|
312
+ check_mapping(profile).each_value do |check|
313
+ parameters = parameters.deep_merge!(check.hiera)
314
+ end
315
+ end
316
+
317
+ @hiera[cache_key] = parameters
318
+ end
319
+
320
+ # Return all checks that map to the requested profile or CE
321
+ #
322
+ # @param [ComplianceEngine::Profile, ComplianceEngine::Ce] profile_or_ce The requested profile or CE
323
+ # @return [Hash]
324
+ def check_mapping(profile_or_ce)
325
+ raise ArgumentError, 'Argument must be a ComplianceEngine::Profile object' unless profile_or_ce.is_a?(ComplianceEngine::Profile) || profile_or_ce.is_a?(ComplianceEngine::Ce)
326
+
327
+ cache_key = "#{profile_or_ce.class}:#{profile_or_ce.key}"
328
+
329
+ @check_mapping ||= {}
330
+
331
+ return @check_mapping[cache_key] if @check_mapping.key?(cache_key)
332
+
333
+ @check_mapping[cache_key] = checks.select do |_, check|
334
+ mapping?(check, profile_or_ce)
335
+ end
336
+ end
337
+
338
+ private
339
+
340
+ # Get the collection variables
341
+ #
342
+ # @return [Array<Symbol>]
343
+ def collection_variables
344
+ [:@profiles, :@checks, :@controls, :@ces]
345
+ end
346
+
347
+ # Get the data variables
348
+ #
349
+ # @return [Array<Symbol>]
350
+ def data_variables
351
+ [:@data]
352
+ end
353
+
354
+ # Get the context variables
355
+ #
356
+ # @return [Array<Symbol>]
357
+ def context_variables
358
+ [:@enforcement_tolerance, :@environment_data, :@facts]
359
+ end
360
+
361
+ # Get the cache variables
362
+ #
363
+ # @return [Array<Symbol>]
364
+ def cache_variables
365
+ instance_variables - (data_variables + collection_variables + context_variables)
366
+ end
367
+
368
+ # Return true if the check is mapped to the profile or CE
369
+ #
370
+ # @param [ComplianceEngine::Check] check The check
371
+ # @param [ComplianceEngine::Profile, ComplianceEngine::Ce] profile_or_ce The profile or CE
372
+ # @return [TrueClass, FalseClass]
373
+ def mapping?(check, profile_or_ce)
374
+ raise ArgumentError, 'Argument must be a ComplianceEngine::Profile object' unless profile_or_ce.is_a?(ComplianceEngine::Profile) || profile_or_ce.is_a?(ComplianceEngine::Ce)
375
+
376
+ @mapping ||= {}
377
+ cache_key = [check.key, "#{profile_or_ce.class}:#{profile_or_ce.key}"].to_s
378
+ return @mapping[cache_key] if @mapping.key?(cache_key)
379
+
380
+ # Correlate based on controls
381
+ controls = check.controls&.select { |_, v| v }&.map { |k, _| k }
382
+
383
+ return @mapping[cache_key] = true if correlate(controls, profile_or_ce.controls)
384
+
385
+ if profile_or_ce.is_a?(ComplianceEngine::Ce)
386
+ # Correlate based on CEs
387
+ return @mapping[cache_key] = true if check.ces&.include?(profile_or_ce.key)
388
+
389
+ return @mapping[cache_key] = false
390
+ end
391
+
392
+ # Correlate based on direct reference to checks
393
+ return @mapping[cache_key] = true if profile_or_ce.checks&.dig(check.key)
394
+
395
+ # Correlate based on CEs
396
+ return @mapping[cache_key] = true if correlate(check.ces, profile_or_ce.ces)
397
+
398
+ # Correlate based on CEs and controls
399
+ return @mapping[cache_key] = true if profile_or_ce.ces&.any? { |k, _| correlate(controls, ces[k]&.controls) }
400
+ return @mapping[cache_key] = true if check.ces&.any? { |ce| ces[ce]&.controls&.any? { |k, v| v && profile_or_ce.controls&.dig(k) } }
401
+
402
+ @mapping[cache_key] = false
403
+ end
404
+
405
+ # Correlate between arrays and hashes
406
+ #
407
+ # @param [Array] a An array
408
+ # @param [Hash] b A hash
409
+ # @return [TrueClass, FalseClass]
410
+ def correlate(a, b)
411
+ return false if a.nil? || b.nil?
412
+ unless a.is_a?(Array) && b.is_a?(Hash)
413
+ raise ComplianceEngine::Error, "Expected array and hash, got #{a.class} and #{b.class}"
414
+ end
415
+ return false if a.empty? || b.empty?
416
+
417
+ a.any? { |item| b[item] }
418
+ end
419
+
420
+ # Print debugging messages to the console.
421
+ #
422
+ # @param [String] msg The message to print
423
+ def debug(msg)
424
+ warn msg
425
+ end
426
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+ require 'compliance_engine/data_loader'
5
+
6
+ # Load compliance engine data from a file
7
+ class ComplianceEngine::DataLoader::File < ComplianceEngine::DataLoader
8
+ # Initialize a new instance of the ComplianceEngine::DataLoader::File class
9
+ #
10
+ # @param [String] file The path to the file to be loaded
11
+ # @param [Class] fileclass The class to use for file operations, defaults to `::File`
12
+ # @param [String] key The key to use for identifying the data, defaults to the file path
13
+ def initialize(file, fileclass: ::File, key: file)
14
+ @fileclass = fileclass
15
+ @filename = file
16
+ @size = fileclass.size(file)
17
+ @mtime = fileclass.mtime(file)
18
+ super(parse(fileclass.read(file)), key: key)
19
+ end
20
+
21
+ # Refresh the data from the file if it has changed
22
+ #
23
+ # @return [NilClass]
24
+ def refresh
25
+ newsize = @fileclass.size(@filename)
26
+ newmtime = @fileclass.mtime(@filename)
27
+ return if newsize == @size && newmtime == @mtime
28
+
29
+ @size = newsize
30
+ @mtime = newmtime
31
+ self.data = parse(@fileclass.read(@filename))
32
+ end
33
+
34
+ attr_reader :key, :size, :mtime
35
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+ require 'compliance_engine/data_loader/file'
5
+
6
+ require 'json'
7
+
8
+ # Load compliance engine data from a JSON file
9
+ class ComplianceEngine::DataLoader::Json < ComplianceEngine::DataLoader::File
10
+ # Parse JSON content into a Hash
11
+ #
12
+ # @param [String] content The content to parse
13
+ # @return [Hash]
14
+ def parse(content)
15
+ JSON.parse(content)
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+ require 'compliance_engine/data_loader/file'
5
+
6
+ require 'yaml'
7
+
8
+ # Load compliance engine data from a YAML file
9
+ class ComplianceEngine::DataLoader::Yaml < ComplianceEngine::DataLoader::File
10
+ # Parse YAML content into a Hash
11
+ #
12
+ # @param [String] content The content to parse
13
+ # @return [Hash]
14
+ def parse(content)
15
+ YAML.safe_load(content)
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+ require 'observer'
5
+
6
+ # Load compliance engine data
7
+ class ComplianceEngine::DataLoader
8
+ include Observable
9
+
10
+ # Initialize a new instance of the ComplianceEngine::DataLoader::File class
11
+ #
12
+ # @param value [Hash] The data to initialize the object with
13
+ # @param [String] key The key to use for identifying the data
14
+ def initialize(value = {}, key: nil)
15
+ self.data = value
16
+ @key = key
17
+ end
18
+
19
+ attr_reader :data
20
+
21
+ # Set the data for the data loader
22
+ #
23
+ # @param value [Hash] The new value for the data loader
24
+ #
25
+ # @raise [ComplianceEngine::Error] If the value is not a Hash
26
+ def data=(value)
27
+ raise ComplianceEngine::Error, 'Data must be a hash' unless value.is_a?(Hash)
28
+ @data = value
29
+ changed
30
+ notify_observers(self)
31
+ end
32
+
33
+ # Get the key for the data loader
34
+ #
35
+ # The key is used to identify the data to observers. If a key is not
36
+ # provided during initialization, a random UUID will be generated.
37
+ #
38
+ # @return [String] The key for the data loader
39
+ def key
40
+ return @key unless @key.nil?
41
+
42
+ require 'securerandom'
43
+ @key = "#{data.class}:#{SecureRandom.uuid}"
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+ require 'compliance_engine/environment_loader'
5
+ require 'zip/filesystem'
6
+
7
+ # Load compliance engine data from a zip file containing a Puppet environment
8
+ class ComplianceEngine::EnvironmentLoader::Zip < ComplianceEngine::EnvironmentLoader
9
+ # Initialize a ComplianceEngine::EnvironmentLoader::Zip object from a zip
10
+ # file and an optional root directory.
11
+ #
12
+ # @param path [String] the path to the zip file containing the Puppet environment
13
+ # @param root [String] a directory within the zip file to use as the root of the environment
14
+ def initialize(path, root: '/'.dup)
15
+ @modulepath = path
16
+
17
+ ::Zip::File.open(path) do |zipfile|
18
+ dir = zipfile.dir
19
+ file = zipfile.file
20
+
21
+ super(path, root: root, fileclass: file, dirclass: dir)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+ require 'compliance_engine/module_loader'
5
+
6
+ # Load compliance engine data from a Puppet environment
7
+ class ComplianceEngine::EnvironmentLoader
8
+ # Initialize an EnvironmentLoader from the components of a Puppet `modulepath`
9
+ #
10
+ # @param paths [Array] the paths to search for Puppet modules
11
+ # @param root [String] the root directory to search for Puppet modules
12
+ # @param fileclass [File] the class to use for file operations (default: `File`)
13
+ # @param dirclass [Dir] the class to use for directory operations (default: `Dir`)
14
+ def initialize(*paths, root: nil, fileclass: File, dirclass: Dir)
15
+ raise ArgumentError, 'No paths specified' if paths.empty?
16
+ @modulepath ||= paths
17
+ modules = paths.map do |path|
18
+ root ||= path
19
+ dirclass.entries(root)
20
+ .grep(%r{\A[a-z][a-z0-9_]*\Z})
21
+ .select { |child| fileclass.directory?(File.join(root, child)) }
22
+ .map { |child| File.join(root, child) }
23
+ rescue
24
+ []
25
+ end
26
+ modules.flatten!
27
+ @modules = modules.map { |path| ComplianceEngine::ModuleLoader.new(path, fileclass: fileclass, dirclass: dirclass) }
28
+ end
29
+
30
+ attr_reader :modulepath, :modules
31
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+ require 'compliance_engine/data_loader/json'
5
+ require 'compliance_engine/data_loader/yaml'
6
+
7
+ # Load compliance engine data from a Puppet module
8
+ class ComplianceEngine::ModuleLoader
9
+ # Initialize a ModuleLoader from a Puppet module path
10
+ #
11
+ # @param path [String] the path to the Puppet module
12
+ # @param fileclass [File] the class to use for file operations (default: `File`)
13
+ # @param dirclass [Dir] the class to use for directory operations (default: `Dir`)
14
+ def initialize(path, fileclass: File, dirclass: Dir)
15
+ raise ComplianceEngine::Error, "#{path} is not a directory" unless fileclass.directory?(path)
16
+
17
+ @name = nil
18
+ @version = nil
19
+ @files = []
20
+
21
+ # Read the Puppet module's metadata.json
22
+ metadata_json = File.join(path.to_s, 'metadata.json')
23
+ if fileclass.exist?(metadata_json)
24
+ begin
25
+ metadata = ComplianceEngine::DataLoader::Json.new(metadata_json, fileclass: fileclass)
26
+ @name = metadata.data['name']
27
+ @version = metadata.data['version']
28
+ rescue => e
29
+ warn "Could not parse #{metadata_json}: #{e.message}"
30
+ end
31
+ end
32
+
33
+ # In this directory, we want to look for all yaml and json files
34
+ # under SIMP/compliance_profiles and simp/compliance_profiles.
35
+ globs = ['SIMP/compliance_profiles', 'simp/compliance_profiles']
36
+ .select { |dir| fileclass.directory?(File.join(path, dir)) }
37
+ .map { |dir|
38
+ ['yaml', 'json'].map { |type| File.join(path, dir, '**', "*.#{type}") }
39
+ }.flatten
40
+ # debug "Globs: #{globs}"
41
+ # Using .each here to make mocking with rspec easier.
42
+ globs.each do |glob|
43
+ dirclass.glob(glob).each do |file|
44
+ key = if Object.const_defined?(:Zip) && file.is_a?(Zip::Entry)
45
+ File.join(file.zipfile.to_s, '.', file.to_s)
46
+ else
47
+ file.to_s
48
+ end
49
+ loader = if File.extname(file.to_s) == '.json'
50
+ ComplianceEngine::DataLoader::Json.new(file.to_s, fileclass: fileclass, key: key)
51
+ else
52
+ ComplianceEngine::DataLoader::Yaml.new(file.to_s, fileclass: fileclass, key: key)
53
+ end
54
+ @files << loader
55
+ rescue => e
56
+ warn "Could not load #{file}: #{e.message}"
57
+ end
58
+ end
59
+ end
60
+
61
+ attr_reader :name, :version, :files
62
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+
5
+ # A compliance engine data profile
6
+ class ComplianceEngine::Profile < ComplianceEngine::Component
7
+ # Returns the checks of the profile
8
+ #
9
+ # @return [Hash] the checks of the profile
10
+ def checks
11
+ element['checks']
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'compliance_engine'
4
+
5
+ # A collection of compliance engine data profiles
6
+ class ComplianceEngine::Profiles < ComplianceEngine::Collection
7
+ private
8
+
9
+ # Returns the key of the collection in compliance engine source data
10
+ #
11
+ # @return [String]
12
+ def key
13
+ 'profiles'
14
+ end
15
+
16
+ # Returns the class to use for the collection
17
+ #
18
+ # @return [Class]
19
+ def collected
20
+ ComplianceEngine::Profile
21
+ end
22
+ end