cem_acpt 0.8.7 → 0.9.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/spec.yml +0 -3
  3. data/Gemfile.lock +9 -1
  4. data/README.md +95 -13
  5. data/cem_acpt.gemspec +2 -1
  6. data/lib/cem_acpt/action_result.rb +8 -2
  7. data/lib/cem_acpt/actions.rb +153 -0
  8. data/lib/cem_acpt/bolt/cmd/base.rb +174 -0
  9. data/lib/cem_acpt/bolt/cmd/output.rb +315 -0
  10. data/lib/cem_acpt/bolt/cmd/task.rb +59 -0
  11. data/lib/cem_acpt/bolt/cmd.rb +22 -0
  12. data/lib/cem_acpt/bolt/errors.rb +49 -0
  13. data/lib/cem_acpt/bolt/helpers.rb +52 -0
  14. data/lib/cem_acpt/bolt/inventory.rb +62 -0
  15. data/lib/cem_acpt/bolt/project.rb +38 -0
  16. data/lib/cem_acpt/bolt/summary_results.rb +96 -0
  17. data/lib/cem_acpt/bolt/tasks.rb +181 -0
  18. data/lib/cem_acpt/bolt/tests.rb +415 -0
  19. data/lib/cem_acpt/bolt/yaml_file.rb +74 -0
  20. data/lib/cem_acpt/bolt.rb +142 -0
  21. data/lib/cem_acpt/cli.rb +6 -0
  22. data/lib/cem_acpt/config/base.rb +4 -0
  23. data/lib/cem_acpt/config/cem_acpt.rb +7 -1
  24. data/lib/cem_acpt/core_ext.rb +25 -0
  25. data/lib/cem_acpt/goss/api/action_response.rb +4 -0
  26. data/lib/cem_acpt/goss/api.rb +23 -25
  27. data/lib/cem_acpt/logging/formatter.rb +3 -3
  28. data/lib/cem_acpt/logging.rb +17 -1
  29. data/lib/cem_acpt/provision/terraform/linux.rb +1 -1
  30. data/lib/cem_acpt/test_data.rb +2 -0
  31. data/lib/cem_acpt/test_runner/log_formatter/base.rb +73 -0
  32. data/lib/cem_acpt/test_runner/log_formatter/bolt_error_formatter.rb +65 -0
  33. data/lib/cem_acpt/test_runner/log_formatter/bolt_output_formatter.rb +54 -0
  34. data/lib/cem_acpt/test_runner/log_formatter/bolt_summary_results_formatter.rb +64 -0
  35. data/lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb +17 -30
  36. data/lib/cem_acpt/test_runner/log_formatter/goss_error_formatter.rb +31 -0
  37. data/lib/cem_acpt/test_runner/log_formatter/standard_error_formatter.rb +35 -0
  38. data/lib/cem_acpt/test_runner/log_formatter.rb +17 -5
  39. data/lib/cem_acpt/test_runner/test_results.rb +150 -0
  40. data/lib/cem_acpt/test_runner.rb +153 -53
  41. data/lib/cem_acpt/utils/files.rb +189 -0
  42. data/lib/cem_acpt/utils/finalizer_queue.rb +73 -0
  43. data/lib/cem_acpt/utils/shell.rb +13 -4
  44. data/lib/cem_acpt/version.rb +1 -1
  45. data/sample_config.yaml +13 -0
  46. metadata +41 -5
  47. data/lib/cem_acpt/test_runner/log_formatter/error_formatter.rb +0 -33
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require_relative '../logging'
5
+
6
+ module CemAcpt
7
+ module Utils
8
+ # Utility classes and methods for files
9
+ module Files
10
+ class << self
11
+ # Reads a file based on its extension
12
+ # @param file [String] Path to the file
13
+ # @param log_level [Symbol] Log level to use
14
+ # @param log_prefix [String] Log prefix to use
15
+ # @param kwargs [Hash] Keyword arguments to pass to the file utility
16
+ # @option kwargs [String] :log_msg Log message to use when logging the file operation
17
+ # @option kwargs [Array] :permitted_classes Array of classes to permit when loading YAML files
18
+ # @return [Object] The result of the file utility's read method
19
+ def read(file, *args, log_level: :debug, log_prefix: 'CemAcpt', **kwargs)
20
+ return from_content_registry(file, :content) unless file_changed?(file)
21
+
22
+ content = new_file_util_for(file, log_level: log_level, log_prefix: log_prefix).read(file, *args, **kwargs)
23
+ add_to_content_registry(file, :content, content)
24
+ content
25
+ end
26
+
27
+ def write(file, content, *args, log_level: :debug, log_prefix: 'CemAcpt', **kwargs)
28
+ new_file_util_for(file, log_level: log_level, log_prefix: log_prefix).write(file, content, *args, **kwargs)
29
+ end
30
+
31
+ def delete(file, *args, log_level: :debug, log_prefix: 'CemAcpt', **kwargs)
32
+ new_file_util_for(file, log_level: log_level, log_prefix: log_prefix).delete(file, *args, **kwargs)
33
+ end
34
+
35
+ private
36
+
37
+ def mutex
38
+ @mutex ||= Mutex.new
39
+ end
40
+
41
+ def content_registry
42
+ @content_registry ||= {}
43
+ end
44
+
45
+ def file_changed?(file)
46
+ return true unless File.exist?(file)
47
+ fstat = File.stat(file)
48
+ rmtime = from_content_registry(file, :mtime)
49
+ check_res = rmtime.nil? || (fstat.mtime != rmtime)
50
+ add_to_content_registry(file, :mtime, fstat.mtime)
51
+ check_res
52
+ end
53
+
54
+ def add_to_content_registry(file, property, value)
55
+ mutex.synchronize do
56
+ content_registry[file] ||= {}
57
+ content_registry[file][property.to_sym] = value
58
+ end
59
+ end
60
+
61
+ def from_content_registry(file, property)
62
+ mutex.synchronize do
63
+ content_registry[file] ||= {}
64
+ content_registry[file][property.to_sym]
65
+ end
66
+ end
67
+
68
+ def new_file_util_for(file, log_level: :debug, log_prefix: 'CemAcpt')
69
+ case File.extname(file)
70
+ when *YamlUtil::VALID_EXTS
71
+ YamlUtil.new(log_level: log_level, log_prefix: log_prefix)
72
+ when *JsonUtil::VALID_EXTS
73
+ JsonUtil.new(log_level: log_level, log_prefix: log_prefix)
74
+ else
75
+ FileUtil.new(log_level: log_level, log_prefix: log_prefix)
76
+ end
77
+ end
78
+ end
79
+
80
+ # Generic file utility class
81
+ class FileUtil
82
+ include CemAcpt::Logging
83
+
84
+ attr_reader :log_level, :log_prefix, :file_exts
85
+
86
+ def initialize(log_level: :debug, log_prefix: 'CemAcpt', file_exts: [])
87
+ @log_level = log_level
88
+ @log_prefix = log_prefix
89
+ @file_exts = file_exts
90
+ end
91
+
92
+ def log_level=(level)
93
+ level = level.downcase.to_sym
94
+ raise ArgumentError, "Invalid log level #{level}" unless logger.respond_to?(level)
95
+
96
+ @log_level = level
97
+ end
98
+
99
+ def log_prefix=(prefix)
100
+ @log_prefix = prefix.to_s
101
+ end
102
+
103
+ def file_exts=(exts)
104
+ raise ArgumentError, 'file_exts must be an Array' unless exts.is_a?(Array)
105
+
106
+ @file_ext = exts
107
+ end
108
+
109
+ def write(file, content, *_args, log_msg: 'Writing file %s...', **_kwargs)
110
+ validate_and_log(file, log_msg)
111
+ File.write(file, content)
112
+ end
113
+
114
+ def read(file, *_args, log_msg: 'Reading file %s...', **_kwargs)
115
+ validate_and_log(file, log_msg)
116
+ File.read(file)
117
+ end
118
+
119
+ def delete(file, *_args, log_msg: 'Deleting file %s...', **_kwargs)
120
+ validate_and_log(file, log_msg)
121
+ FileUtils.rm_f(file)
122
+ end
123
+
124
+ private
125
+
126
+ def validate_and_log(file, log_msg)
127
+ file = validate_ext(file)
128
+ logger.send(log_level, log_prefix) { log_msg.to_s % file }
129
+ end
130
+
131
+ def validate_ext(file)
132
+ return if file_exts.empty?
133
+
134
+ ext = File.extname(file)
135
+ raise ArgumentError, "Invalid file extension #{ext}! Valid file extensions are #{file_exts}" unless file_exts.include?(ext)
136
+ end
137
+ end
138
+
139
+ # Utility class for working with YAML files
140
+ class YamlUtil < FileUtil
141
+ VALID_EXTS = %w[.yaml .yml].freeze
142
+ DEFAULT_PERMITTED_CLASSES = [Symbol].freeze
143
+
144
+ def initialize(log_level: :debug, log_prefix: 'CemAcpt', file_exts: VALID_EXTS)
145
+ super(log_level: log_level, log_prefix: log_prefix, file_exts: file_exts)
146
+ require 'yaml'
147
+ end
148
+
149
+ def write(file, content, *_args, log_msg: 'Writing YAML file %s...', **_kwargs)
150
+ raise ArgumentError, 'content must be a Hash' unless content.is_a?(Hash)
151
+
152
+ super(file, content.to_yaml, log_msg)
153
+ end
154
+
155
+ def read(file, *_args, log_msg: 'Reading YAML file %s...', permitted_classes: DEFAULT_PERMITTED_CLASSES, **_kwargs)
156
+ YAML.safe_load(super(file, log_msg), permitted_classes: permitted_classes)
157
+ end
158
+
159
+ def delete(file, *_args, log_msg: 'Deleting YAML file %s...', **_kwargs)
160
+ super(file, log_msg)
161
+ end
162
+ end
163
+
164
+ # Utility class for working with JSON files
165
+ class JsonUtil < FileUtil
166
+ VALID_EXTS = %w[.json].freeze
167
+
168
+ def initialize(log_level: :debug, log_prefix: 'CemAcpt', file_exts: VALID_EXTS)
169
+ super(log_level: log_level, log_prefix: log_prefix, file_exts: file_exts)
170
+ require 'json'
171
+ end
172
+
173
+ def write(file, content, *_args, log_msg: 'Writing JSON file %s...', **_kwargs)
174
+ raise ArgumentError, 'content must be a Hash' unless content.is_a?(Hash)
175
+
176
+ super(file, content.to_json, log_msg)
177
+ end
178
+
179
+ def read(file, *_args, log_msg: 'Reading JSON file %s...', **_kwargs)
180
+ JSON.parse(super(file, log_msg))
181
+ end
182
+
183
+ def delete(file, *_args, log_msg: 'Deleting JSON file %s...', **_kwargs)
184
+ super(file, log_msg)
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CemAcpt
4
+ module Utils
5
+ class FinalizerQueueError < StandardError; end
6
+
7
+ # A queue that can be finalized.
8
+ # When a queue is finalized, no more items can be added to it, and the
9
+ # queue is closed and converted to a frozen array.
10
+ class FinalizerQueue
11
+ def initialize
12
+ @queue = Queue.new
13
+ @array = []
14
+ @finalized = false
15
+ @mutex = Mutex.new
16
+ end
17
+
18
+ def finalize!
19
+ return if finalized?
20
+
21
+ @finalized = true
22
+ new_array
23
+ end
24
+
25
+ def finalized?
26
+ @finalized
27
+ end
28
+
29
+ def to_a
30
+ raise FinalizerQueueError, 'Cannot convert to array until finalized' unless finalized?
31
+
32
+ @array
33
+ end
34
+
35
+ def method_missing(method_name, *args, **kwargs, &block)
36
+ if finalized?
37
+ @array.send(method_name, *args, **kwargs, &block)
38
+ elsif @queue.respond_to?(method_name)
39
+ @queue.send(method_name, *args, **kwargs, &block)
40
+ else
41
+ super
42
+ end
43
+ rescue StandardError => e
44
+ raise e if e.is_a?(NoMethodError) || e.is_a?(FinalizerQueueError)
45
+
46
+ new_err = FinalizerQueueError.new("Error calling #{method_name} on FinalizerQueue: #{e}")
47
+ new_err.set_backtrace(e.backtrace)
48
+ raise new_err
49
+ end
50
+
51
+ def respond_to_missing?(method_name, include_private = false)
52
+ @array.respond_to?(method_name, include_private) if finalized?
53
+
54
+ super
55
+ end
56
+
57
+ private
58
+
59
+ def new_array
60
+ @queue.close unless @queue.closed?
61
+ @array << @queue.pop until @queue.empty?
62
+ @array.compact!
63
+ @array.freeze
64
+ end
65
+
66
+ def require_finalized(caller_binding)
67
+ return if finalized?
68
+
69
+ raise FinalizerQueueError, "Cannot call #{caller_binding.eval('__method__')} on unfinalized #{self.class.name}"
70
+ end
71
+ end
72
+ end
73
+ end
@@ -6,6 +6,7 @@ require 'stringio'
6
6
  module CemAcpt
7
7
  # Error class for shell commands
8
8
  class ShellCommandError < StandardError; end
9
+ class ShellCommandNotFoundError < ShellCommandError; end
9
10
 
10
11
  module Utils
11
12
  # Generic utilities for running local shell commands
@@ -23,7 +24,7 @@ module CemAcpt
23
24
  io_outerr = StringIO.new
24
25
  if output.respond_to?(:debug)
25
26
  output.debug('CemAcpt::Utils::Shell') { "Running command:\n\t#{cmd}\nWith environment:\n\t#{env}" }
26
- else
27
+ elsif output
27
28
  output << "Running command:\n\t#{cmd}\nWith environment:\n\t#{env}\n"
28
29
  end
29
30
  val = Open3.popen2e(env, cmd) do |stdin, outerr, wait_thr|
@@ -47,18 +48,26 @@ module CemAcpt
47
48
 
48
49
  # Mimics the behavior of the `which` command.
49
50
  # @param cmd [String] The command to find
50
- # @return [String] The path to the command
51
- # @return [nil] If the command is not found
52
- def self.which(cmd)
51
+ # @param include_ruby_bin [Boolean] Whether to include Ruby bin directories in the search.
52
+ # Setting this to true can cause errors to be raised if cem_acpt attempts to use a Ruby
53
+ # command that is not available to cem_acpt, such as when running with `bundle exec`.
54
+ # @param raise_if_not_found [Boolean] Whether to raise an error if the command is not found
55
+ # @return [String, nil] The path to the command or nil if not found
56
+ # @raise [CemAcpt::ShellCommandNotFoundError] If the command is not found and raise_if_not_found is true
57
+ def self.which(cmd, include_ruby_bin: false, raise_if_not_found: false)
53
58
  return cmd if File.executable?(cmd) && !File.directory?(cmd)
54
59
 
55
60
  exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
56
61
  ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
62
+ next if path.include?('/ruby') && !include_ruby_bin
63
+
57
64
  exts.each do |ext|
58
65
  exe = File.join(path, "#{cmd}#{ext}")
59
66
  return exe if File.executable?(exe) && !File.directory?(exe)
60
67
  end
61
68
  end
69
+ raise CemAcpt::ShellCommandNotFoundError, "Command #{cmd} not found in PATH" if raise_if_not_found
70
+
62
71
  nil
63
72
  end
64
73
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CemAcpt
4
- VERSION = '0.8.7'
4
+ VERSION = '0.9.0'
5
5
  end
data/sample_config.yaml CHANGED
@@ -58,6 +58,19 @@ tests:
58
58
  # - stig_rhel-7_firewalld_public_3
59
59
  # - stig_rhel-8_firewalld_public_3
60
60
 
61
+ bolt:
62
+ project:
63
+ name: 'cem-acpt'
64
+ analytics: false
65
+ tests:
66
+ only: [] # Test names from the "tests" array above
67
+ ignore: []
68
+ tasks:
69
+ ignore: [] # Task names to ignore
70
+ only: []
71
+ module_pattern: '^.*$'
72
+ name_filter: '^fake_task$'
73
+
61
74
  cem_acpt_image:
62
75
  no_windows: true
63
76
  no_linux: false
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cem_acpt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.7
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - puppetlabs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-10-25 00:00:00.000000000 Z
11
+ date: 2024-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-http
@@ -180,6 +180,20 @@ dependencies:
180
180
  - - ">="
181
181
  - !ruby/object:Gem::Version
182
182
  version: '0'
183
+ - !ruby/object:Gem::Dependency
184
+ name: simplecov
185
+ requirement: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ type: :development
191
+ prerelease: false
192
+ version_requirements: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
183
197
  description: Litmus-like library focusing on CEM Acceptance Tests
184
198
  email:
185
199
  - abide-team@puppet.com
@@ -205,6 +219,20 @@ files:
205
219
  - exe/cem_acpt_image
206
220
  - lib/cem_acpt.rb
207
221
  - lib/cem_acpt/action_result.rb
222
+ - lib/cem_acpt/actions.rb
223
+ - lib/cem_acpt/bolt.rb
224
+ - lib/cem_acpt/bolt/cmd.rb
225
+ - lib/cem_acpt/bolt/cmd/base.rb
226
+ - lib/cem_acpt/bolt/cmd/output.rb
227
+ - lib/cem_acpt/bolt/cmd/task.rb
228
+ - lib/cem_acpt/bolt/errors.rb
229
+ - lib/cem_acpt/bolt/helpers.rb
230
+ - lib/cem_acpt/bolt/inventory.rb
231
+ - lib/cem_acpt/bolt/project.rb
232
+ - lib/cem_acpt/bolt/summary_results.rb
233
+ - lib/cem_acpt/bolt/tasks.rb
234
+ - lib/cem_acpt/bolt/tests.rb
235
+ - lib/cem_acpt/bolt/yaml_file.rb
208
236
  - lib/cem_acpt/cli.rb
209
237
  - lib/cem_acpt/config.rb
210
238
  - lib/cem_acpt/config/base.rb
@@ -233,9 +261,17 @@ files:
233
261
  - lib/cem_acpt/test_data.rb
234
262
  - lib/cem_acpt/test_runner.rb
235
263
  - lib/cem_acpt/test_runner/log_formatter.rb
236
- - lib/cem_acpt/test_runner/log_formatter/error_formatter.rb
264
+ - lib/cem_acpt/test_runner/log_formatter/base.rb
265
+ - lib/cem_acpt/test_runner/log_formatter/bolt_error_formatter.rb
266
+ - lib/cem_acpt/test_runner/log_formatter/bolt_output_formatter.rb
267
+ - lib/cem_acpt/test_runner/log_formatter/bolt_summary_results_formatter.rb
237
268
  - lib/cem_acpt/test_runner/log_formatter/goss_action_response.rb
269
+ - lib/cem_acpt/test_runner/log_formatter/goss_error_formatter.rb
270
+ - lib/cem_acpt/test_runner/log_formatter/standard_error_formatter.rb
271
+ - lib/cem_acpt/test_runner/test_results.rb
238
272
  - lib/cem_acpt/utils.rb
273
+ - lib/cem_acpt/utils/files.rb
274
+ - lib/cem_acpt/utils/finalizer_queue.rb
239
275
  - lib/cem_acpt/utils/puppet.rb
240
276
  - lib/cem_acpt/utils/shell.rb
241
277
  - lib/cem_acpt/utils/ssh.rb
@@ -270,14 +306,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
270
306
  requirements:
271
307
  - - ">="
272
308
  - !ruby/object:Gem::Version
273
- version: 2.6.0
309
+ version: 3.0.0
274
310
  required_rubygems_version: !ruby/object:Gem::Requirement
275
311
  requirements:
276
312
  - - ">="
277
313
  - !ruby/object:Gem::Version
278
314
  version: '0'
279
315
  requirements: []
280
- rubygems_version: 3.4.19
316
+ rubygems_version: 3.4.22
281
317
  signing_key:
282
318
  specification_version: 4
283
319
  summary: CEM Acceptance Tests
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CemAcpt
4
- module TestRunner
5
- module LogFormatter
6
- class ErrorFormatter
7
- def inspect
8
- to_s
9
- end
10
-
11
- def to_s
12
- "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
13
- end
14
-
15
- def summary(response)
16
- "Error: #{response.summary}"
17
- end
18
-
19
- def results(response)
20
- [response.summary, response.results.join("\n")]
21
- end
22
-
23
- def host_name(response)
24
- "Error: #{response.error.class.name}"
25
- end
26
-
27
- def test_name(response)
28
- "Error: #{response.error.class.name}"
29
- end
30
- end
31
- end
32
- end
33
- end